@attestry/sdk 0.6.0 → 0.7.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/annex-iv-verify/data-integrity.d.ts +57 -0
- package/dist/annex-iv-verify/data-integrity.d.ts.map +1 -0
- package/dist/annex-iv-verify/data-integrity.js +172 -0
- package/dist/annex-iv-verify/data-integrity.js.map +1 -0
- package/dist/annex-iv-verify/ed25519.d.ts +21 -0
- package/dist/annex-iv-verify/ed25519.d.ts.map +1 -0
- package/dist/annex-iv-verify/ed25519.js +67 -0
- package/dist/annex-iv-verify/ed25519.js.map +1 -0
- package/dist/annex-iv-verify/index.d.ts +4 -0
- package/dist/annex-iv-verify/index.d.ts.map +1 -0
- package/dist/annex-iv-verify/index.js +11 -0
- package/dist/annex-iv-verify/index.js.map +1 -0
- package/dist/annex-iv-verify/jwk.d.ts +27 -0
- package/dist/annex-iv-verify/jwk.d.ts.map +1 -0
- package/dist/annex-iv-verify/jwk.js +57 -0
- package/dist/annex-iv-verify/jwk.js.map +1 -0
- package/dist/annex-iv-verify/multibase.d.ts +31 -0
- package/dist/annex-iv-verify/multibase.d.ts.map +1 -0
- package/dist/annex-iv-verify/multibase.js +131 -0
- package/dist/annex-iv-verify/multibase.js.map +1 -0
- package/dist/annex-iv-verify/resolver.d.ts +28 -0
- package/dist/annex-iv-verify/resolver.d.ts.map +1 -0
- package/dist/annex-iv-verify/resolver.js +58 -0
- package/dist/annex-iv-verify/resolver.js.map +1 -0
- package/dist/annex-iv-verify/status-list.d.ts +57 -0
- package/dist/annex-iv-verify/status-list.d.ts.map +1 -0
- package/dist/annex-iv-verify/status-list.js +185 -0
- package/dist/annex-iv-verify/status-list.js.map +1 -0
- package/dist/annex-iv-verify/verify.d.ts +164 -0
- package/dist/annex-iv-verify/verify.d.ts.map +1 -0
- package/dist/annex-iv-verify/verify.js +273 -0
- package/dist/annex-iv-verify/verify.js.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -0
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/resources/annex-iv.d.ts +110 -0
- package/dist/resources/annex-iv.d.ts.map +1 -0
- package/dist/resources/annex-iv.js +146 -0
- package/dist/resources/annex-iv.js.map +1 -0
- package/dist/transport.js +1 -1
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +7 -2
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// VERIFY-only faithful port of `src/lib/crypto/multibase.ts` — the kernel is the
|
|
2
|
+
// source of truth; the golden cross-vectors (`__tests__/golden-vectors.json`)
|
|
3
|
+
// break first on any divergence. Do NOT edit semantics independently.
|
|
4
|
+
//
|
|
5
|
+
// Copied VERBATIM. Dependency-free (no imports). The sign-side encoders carried
|
|
6
|
+
// here are dead-but-private; only `decodeMultibaseBase58btc` is verifier-reached.
|
|
7
|
+
/**
|
|
8
|
+
* Multibase / multicodec encodings used by the W3C VC Data Integrity stack.
|
|
9
|
+
*
|
|
10
|
+
* Two distinct encodings live here, both base58btc with the multibase `z`
|
|
11
|
+
* prefix but with different payloads — keeping them straight matters:
|
|
12
|
+
*
|
|
13
|
+
* - **proofValue** (eddsa-jcs-2022): `z` + base58btc(rawSignatureBytes). No
|
|
14
|
+
* multicodec prefix. This is exactly what `@digitalbazaar/data-integrity`
|
|
15
|
+
* emits/consumes.
|
|
16
|
+
* - **publicKeyMultibase** (Multikey): `z` + base58btc(0xed01 ‖ rawPublicKey).
|
|
17
|
+
* The `0xed01` multicodec header is what makes it render as `z6Mk…` and is
|
|
18
|
+
* what `@digitalbazaar/ed25519-multikey` requires to resolve the key.
|
|
19
|
+
*
|
|
20
|
+
* Implemented dependency-free (no Buffer, no external base58 pkg) so the same
|
|
21
|
+
* code can power a standalone offline verifier later. Cross-checked against
|
|
22
|
+
* `base58-universal` + `@digitalbazaar/ed25519-multikey` in the conformance test.
|
|
23
|
+
*/
|
|
24
|
+
const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
25
|
+
// Reverse lookup: char code -> value (255 = invalid).
|
|
26
|
+
const BASE58_MAP = (() => {
|
|
27
|
+
const map = new Int16Array(128).fill(-1);
|
|
28
|
+
for (let i = 0; i < BASE58_ALPHABET.length; i++) {
|
|
29
|
+
map[BASE58_ALPHABET.charCodeAt(i)] = i;
|
|
30
|
+
}
|
|
31
|
+
return map;
|
|
32
|
+
})();
|
|
33
|
+
/** Encode raw bytes as base58btc (Bitcoin alphabet), preserving leading zeros. */
|
|
34
|
+
export function base58btcEncode(bytes) {
|
|
35
|
+
if (bytes.length === 0)
|
|
36
|
+
return "";
|
|
37
|
+
let zeros = 0;
|
|
38
|
+
while (zeros < bytes.length && bytes[zeros] === 0)
|
|
39
|
+
zeros++;
|
|
40
|
+
// Upper bound on output size: log(256)/log(58) ≈ 1.3658.
|
|
41
|
+
const size = Math.floor((bytes.length - zeros) * 138 / 100) + 1;
|
|
42
|
+
const buffer = new Uint8Array(size);
|
|
43
|
+
let length = 0;
|
|
44
|
+
for (let i = zeros; i < bytes.length; i++) {
|
|
45
|
+
let carry = bytes[i];
|
|
46
|
+
let j = size - 1;
|
|
47
|
+
for (; (carry !== 0 || j >= size - length) && j >= 0; j--) {
|
|
48
|
+
carry += 256 * buffer[j];
|
|
49
|
+
buffer[j] = carry % 58;
|
|
50
|
+
carry = Math.floor(carry / 58);
|
|
51
|
+
}
|
|
52
|
+
length = size - 1 - j;
|
|
53
|
+
}
|
|
54
|
+
let it = size - length;
|
|
55
|
+
while (it < size && buffer[it] === 0)
|
|
56
|
+
it++;
|
|
57
|
+
let out = "1".repeat(zeros);
|
|
58
|
+
for (; it < size; it++)
|
|
59
|
+
out += BASE58_ALPHABET[buffer[it]];
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
/** Decode a base58btc string back to raw bytes, preserving leading-`1` zeros. */
|
|
63
|
+
export function base58btcDecode(str) {
|
|
64
|
+
if (str.length === 0)
|
|
65
|
+
return new Uint8Array(0);
|
|
66
|
+
let zeros = 0;
|
|
67
|
+
while (zeros < str.length && str[zeros] === "1")
|
|
68
|
+
zeros++;
|
|
69
|
+
const size = Math.floor((str.length - zeros) * 733 / 1000) + 1; // log(58)/log(256)
|
|
70
|
+
const buffer = new Uint8Array(size);
|
|
71
|
+
let length = 0;
|
|
72
|
+
for (let i = zeros; i < str.length; i++) {
|
|
73
|
+
const code = str.charCodeAt(i);
|
|
74
|
+
const value = code < 128 ? BASE58_MAP[code] : -1;
|
|
75
|
+
if (value < 0) {
|
|
76
|
+
throw new Error(`Invalid base58 character "${str[i]}" at index ${i}`);
|
|
77
|
+
}
|
|
78
|
+
let carry = value;
|
|
79
|
+
let j = size - 1;
|
|
80
|
+
for (; (carry !== 0 || j >= size - length) && j >= 0; j--) {
|
|
81
|
+
carry += 58 * buffer[j];
|
|
82
|
+
buffer[j] = carry % 256;
|
|
83
|
+
carry = Math.floor(carry / 256);
|
|
84
|
+
}
|
|
85
|
+
length = size - 1 - j;
|
|
86
|
+
}
|
|
87
|
+
let it = size - length;
|
|
88
|
+
const out = new Uint8Array(zeros + (size - it));
|
|
89
|
+
// leading zeros are already 0 in `out`
|
|
90
|
+
let k = zeros;
|
|
91
|
+
for (; it < size; it++, k++)
|
|
92
|
+
out[k] = buffer[it];
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
// ─── Multibase (base58btc, `z` prefix) ───────────────────────────────────────
|
|
96
|
+
export const MULTIBASE_BASE58BTC_PREFIX = "z";
|
|
97
|
+
/** `z` + base58btc(bytes) — multibase base58btc, the proofValue encoding. */
|
|
98
|
+
export function encodeMultibaseBase58btc(bytes) {
|
|
99
|
+
return MULTIBASE_BASE58BTC_PREFIX + base58btcEncode(bytes);
|
|
100
|
+
}
|
|
101
|
+
/** Decode a multibase base58btc (`z`-prefixed) string. Fails closed otherwise. */
|
|
102
|
+
export function decodeMultibaseBase58btc(value) {
|
|
103
|
+
if (value[0] !== MULTIBASE_BASE58BTC_PREFIX) {
|
|
104
|
+
throw new Error(`Unsupported multibase prefix "${value[0] ?? ""}" (only base58btc "z" is supported)`);
|
|
105
|
+
}
|
|
106
|
+
return base58btcDecode(value.slice(1));
|
|
107
|
+
}
|
|
108
|
+
// ─── Ed25519 publicKeyMultibase (Multikey, 0xed01 multicodec) ────────────────
|
|
109
|
+
// Varint encoding of the multicodec `ed25519-pub` code (0xed) -> [0xed, 0x01].
|
|
110
|
+
const MULTICODEC_ED25519_PUB_HEADER = Uint8Array.from([0xed, 0x01]);
|
|
111
|
+
/** Encode a raw 32-byte Ed25519 public key as `z6Mk…` publicKeyMultibase. */
|
|
112
|
+
export function encodeEd25519PublicKeyMultibase(publicKey) {
|
|
113
|
+
if (publicKey.length !== 32) {
|
|
114
|
+
throw new Error(`Ed25519 public key must be 32 bytes, got ${publicKey.length}`);
|
|
115
|
+
}
|
|
116
|
+
const prefixed = new Uint8Array(MULTICODEC_ED25519_PUB_HEADER.length + publicKey.length);
|
|
117
|
+
prefixed.set(MULTICODEC_ED25519_PUB_HEADER, 0);
|
|
118
|
+
prefixed.set(publicKey, MULTICODEC_ED25519_PUB_HEADER.length);
|
|
119
|
+
return encodeMultibaseBase58btc(prefixed);
|
|
120
|
+
}
|
|
121
|
+
/** Decode a `z6Mk…` publicKeyMultibase back to the raw 32-byte public key. */
|
|
122
|
+
export function decodeEd25519PublicKeyMultibase(value) {
|
|
123
|
+
const bytes = decodeMultibaseBase58btc(value);
|
|
124
|
+
if (bytes.length !== 34 ||
|
|
125
|
+
bytes[0] !== MULTICODEC_ED25519_PUB_HEADER[0] ||
|
|
126
|
+
bytes[1] !== MULTICODEC_ED25519_PUB_HEADER[1]) {
|
|
127
|
+
throw new Error("Not a valid Ed25519 publicKeyMultibase (expected 0xed01 multicodec header)");
|
|
128
|
+
}
|
|
129
|
+
return bytes.slice(2);
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=multibase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multibase.js","sourceRoot":"","sources":["../../src/annex-iv-verify/multibase.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,8EAA8E;AAC9E,sEAAsE;AACtE,EAAE;AACF,gFAAgF;AAChF,kFAAkF;AAClF;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,eAAe,GACnB,4DAA4D,CAAC;AAE/D,sDAAsD;AACtD,MAAM,UAAU,GAAe,CAAC,GAAG,EAAE;IACnC,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,eAAe,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChD,GAAG,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC,EAAE,CAAC;AAEL,kFAAkF;AAClF,MAAM,UAAU,eAAe,CAAC,KAAiB;IAC/C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAElC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,KAAK,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,KAAK,EAAE,CAAC;IAE3D,yDAAyD;IACzD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,IAAI,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;QACjB,OAAO,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1D,KAAK,IAAI,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACzB,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;YACvB,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;QACjC,CAAC;QACD,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,EAAE,GAAG,IAAI,GAAG,MAAM,CAAC;IACvB,OAAO,EAAE,GAAG,IAAI,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,CAAC;IAE3C,IAAI,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B,OAAO,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE;QAAE,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3D,OAAO,GAAG,CAAC;AACb,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IAE/C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,KAAK,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG;QAAE,KAAK,EAAE,CAAC;IAEzD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,mBAAmB;IACnF,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,KAAK,GAAG,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,6BAA6B,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC;QACD,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;QACjB,OAAO,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1D,KAAK,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACxB,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,GAAG,CAAC;YACxB,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC;QAClC,CAAC;QACD,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,EAAE,GAAG,IAAI,GAAG,MAAM,CAAC;IACvB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;IAChD,uCAAuC;IACvC,IAAI,CAAC,GAAG,KAAK,CAAC;IACd,OAAO,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IACjD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,gFAAgF;AAEhF,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAE9C,6EAA6E;AAC7E,MAAM,UAAU,wBAAwB,CAAC,KAAiB;IACxD,OAAO,0BAA0B,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,wBAAwB,CAAC,KAAa;IACpD,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,0BAA0B,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CACb,iCAAiC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,qCAAqC,CACrF,CAAC;IACJ,CAAC;IACD,OAAO,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,gFAAgF;AAEhF,+EAA+E;AAC/E,MAAM,6BAA6B,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAEpE,6EAA6E;AAC7E,MAAM,UAAU,+BAA+B,CAAC,SAAqB;IACnE,IAAI,SAAS,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,4CAA4C,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IAClF,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,6BAA6B,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IACzF,QAAQ,CAAC,GAAG,CAAC,6BAA6B,EAAE,CAAC,CAAC,CAAC;IAC/C,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,6BAA6B,CAAC,MAAM,CAAC,CAAC;IAC9D,OAAO,wBAAwB,CAAC,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,+BAA+B,CAAC,KAAa;IAC3D,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAC;IAC9C,IACE,KAAK,CAAC,MAAM,KAAK,EAAE;QACnB,KAAK,CAAC,CAAC,CAAC,KAAK,6BAA6B,CAAC,CAAC,CAAC;QAC7C,KAAK,CAAC,CAAC,CAAC,KAAK,6BAA6B,CAAC,CAAC,CAAC,EAC7C,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,4EAA4E,CAAC,CAAC;IAChG,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Ed25519PublicJwk } from "./jwk.js";
|
|
2
|
+
import type { PublicKeyResolver } from "./data-integrity.js";
|
|
3
|
+
export type { Ed25519PublicJwk } from "./jwk.js";
|
|
4
|
+
/** Extract the kid (fragment) from a verificationMethod URL, or null. (Verbatim from key-registry.) */
|
|
5
|
+
export declare function kidFromVerificationMethod(verificationMethod: string): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* Build a {@link PublicKeyResolver} from a JWKS (an array of public JWKs, or a
|
|
8
|
+
* `{ keys }` object). The resolver matches the verificationMethod's **kid
|
|
9
|
+
* fragment ONLY** against `jwk.kid` and returns the raw 32-byte public key — it
|
|
10
|
+
* does NOT validate the verificationMethod's origin / base-URL (unlike the
|
|
11
|
+
* kernel's server-side registry resolver). Key TRUST is the caller's: supply
|
|
12
|
+
* ONLY JWKs you trust as this issuer's; the Ed25519 signature is still required.
|
|
13
|
+
*
|
|
14
|
+
* **Fail-closed-to-null (C-C-9), NEVER throws** — this is OUR resolver's
|
|
15
|
+
* deliberate contract, DISTINCT from the caller-supplied-resolver propagation:
|
|
16
|
+
* an absent kid match → `null`; a PRESENT-but-malformed jwk → `null` (the
|
|
17
|
+
* `jwkToEd25519PublicKey` throw is caught — it throws on a non-OKP jwk, an
|
|
18
|
+
* over-long `x`, or a decode ≠ 32 bytes); a malformed CONTAINER (not an array
|
|
19
|
+
* and no `keys` array, or a null / primitive entry) → `null` (normalized to an
|
|
20
|
+
* empty list / skipped — never a bare `TypeError`). A `null` return drives the
|
|
21
|
+
* verifier's `valid:false` "public key did not resolve offline" path —
|
|
22
|
+
* kernel-consistent (the kernel's own default resolver returns null on failure,
|
|
23
|
+
* never throws).
|
|
24
|
+
*/
|
|
25
|
+
export declare function makeJwksResolver(jwks: Ed25519PublicJwk[] | {
|
|
26
|
+
keys: Ed25519PublicJwk[];
|
|
27
|
+
}): PublicKeyResolver;
|
|
28
|
+
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/annex-iv-verify/resolver.ts"],"names":[],"mappings":"AAQA,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAEjD,uGAAuG;AACvG,wBAAgB,yBAAyB,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAInF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,gBAAgB,EAAE,GAAG;IAAE,IAAI,EAAE,gBAAgB,EAAE,CAAA;CAAE,GACtD,iBAAiB,CAuBnB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// SDK-only JWKS → resolver bridge for the offline Annex IV bind verifier.
|
|
2
|
+
//
|
|
3
|
+
// This file is NOT a kernel port — it is the SDK's caller-facing key-resolution
|
|
4
|
+
// convenience (the kernel resolves from its env key-registry, which the SDK does
|
|
5
|
+
// NOT ship). `kidFromVerificationMethod` IS lifted verbatim from
|
|
6
|
+
// `src/lib/crypto/key-registry.ts:105` (a pure string fn — the ONLY symbol
|
|
7
|
+
// lifted from key-registry; the rest of that module is server-only).
|
|
8
|
+
import { jwkToEd25519PublicKey, } from "./jwk.js";
|
|
9
|
+
/** Extract the kid (fragment) from a verificationMethod URL, or null. (Verbatim from key-registry.) */
|
|
10
|
+
export function kidFromVerificationMethod(verificationMethod) {
|
|
11
|
+
const hashIdx = verificationMethod.lastIndexOf("#");
|
|
12
|
+
if (hashIdx < 0)
|
|
13
|
+
return null;
|
|
14
|
+
return verificationMethod.slice(hashIdx + 1) || null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build a {@link PublicKeyResolver} from a JWKS (an array of public JWKs, or a
|
|
18
|
+
* `{ keys }` object). The resolver matches the verificationMethod's **kid
|
|
19
|
+
* fragment ONLY** against `jwk.kid` and returns the raw 32-byte public key — it
|
|
20
|
+
* does NOT validate the verificationMethod's origin / base-URL (unlike the
|
|
21
|
+
* kernel's server-side registry resolver). Key TRUST is the caller's: supply
|
|
22
|
+
* ONLY JWKs you trust as this issuer's; the Ed25519 signature is still required.
|
|
23
|
+
*
|
|
24
|
+
* **Fail-closed-to-null (C-C-9), NEVER throws** — this is OUR resolver's
|
|
25
|
+
* deliberate contract, DISTINCT from the caller-supplied-resolver propagation:
|
|
26
|
+
* an absent kid match → `null`; a PRESENT-but-malformed jwk → `null` (the
|
|
27
|
+
* `jwkToEd25519PublicKey` throw is caught — it throws on a non-OKP jwk, an
|
|
28
|
+
* over-long `x`, or a decode ≠ 32 bytes); a malformed CONTAINER (not an array
|
|
29
|
+
* and no `keys` array, or a null / primitive entry) → `null` (normalized to an
|
|
30
|
+
* empty list / skipped — never a bare `TypeError`). A `null` return drives the
|
|
31
|
+
* verifier's `valid:false` "public key did not resolve offline" path —
|
|
32
|
+
* kernel-consistent (the kernel's own default resolver returns null on failure,
|
|
33
|
+
* never throws).
|
|
34
|
+
*/
|
|
35
|
+
export function makeJwksResolver(jwks) {
|
|
36
|
+
return (vm) => {
|
|
37
|
+
// Normalize to a list FIRST — fail-closed-to-null on ANY malformed container
|
|
38
|
+
// shape (not an array, no `keys` array): resolve to [] → null, NEVER a bare
|
|
39
|
+
// TypeError. Do NOT call `.find` on a `{keys}` object or a non-array.
|
|
40
|
+
const list = Array.isArray(jwks)
|
|
41
|
+
? jwks
|
|
42
|
+
: Array.isArray(jwks?.keys)
|
|
43
|
+
? jwks.keys
|
|
44
|
+
: [];
|
|
45
|
+
const kid = kidFromVerificationMethod(vm);
|
|
46
|
+
// Skip null / primitive entries so a malformed entry can never throw on `.kid`.
|
|
47
|
+
const jwk = list.find((j) => j != null && typeof j === "object" && j.kid === kid);
|
|
48
|
+
if (!jwk)
|
|
49
|
+
return null;
|
|
50
|
+
try {
|
|
51
|
+
return jwkToEd25519PublicKey(jwk);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../src/annex-iv-verify/resolver.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,gFAAgF;AAChF,iFAAiF;AACjF,iEAAiE;AACjE,2EAA2E;AAC3E,qEAAqE;AAErE,OAAO,EACL,qBAAqB,GAEtB,MAAM,UAAU,CAAC;AAKlB,uGAAuG;AACvG,MAAM,UAAU,yBAAyB,CAAC,kBAA0B;IAClE,MAAM,OAAO,GAAG,kBAAkB,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACpD,IAAI,OAAO,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7B,OAAO,kBAAkB,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAuD;IAEvD,OAAO,CAAC,EAAU,EAAE,EAAE;QACpB,6EAA6E;QAC7E,4EAA4E;QAC5E,sEAAsE;QACtE,MAAM,IAAI,GAAc,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YACzC,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,KAAK,CAAC,OAAO,CAAE,IAA8C,EAAE,IAAI,CAAC;gBACpE,CAAC,CAAE,IAA4B,CAAC,IAAI;gBACpC,CAAC,CAAC,EAAE,CAAC;QACT,MAAM,GAAG,GAAG,yBAAyB,CAAC,EAAE,CAAC,CAAC;QAC1C,gFAAgF;QAChF,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CACnB,CAAC,CAAC,EAAyB,EAAE,CAC3B,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAK,CAAuB,CAAC,GAAG,KAAK,GAAG,CAC7E,CAAC;QACF,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,IAAI,CAAC;YACH,OAAO,qBAAqB,CAAC,GAAG,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type JsonLdDocument, type SecuredDocument, type PublicKeyResolver, type SignOptions } from "./data-integrity.js";
|
|
2
|
+
/** Minimum list length per spec: 16 KiB = 131,072 single-bit entries. */
|
|
3
|
+
export declare const DEFAULT_STATUS_LIST_LENGTH = 131072;
|
|
4
|
+
/**
|
|
5
|
+
* Hard cap on a DECODED status-list bitstring (10 MiB ≈ 83M entries). The offline
|
|
6
|
+
* verifier gunzips an attacker-supplied `encodedList`; this bounds a gzip bomb.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MAX_DECODED_STATUS_LIST_BYTES: number;
|
|
9
|
+
export declare const MULTIBASE_BASE64URL_NOPAD_PREFIX = "u";
|
|
10
|
+
export declare const BITSTRING_STATUS_LIST_ENTRY = "BitstringStatusListEntry";
|
|
11
|
+
export declare const BITSTRING_STATUS_LIST = "BitstringStatusList";
|
|
12
|
+
export type StatusPurpose = "revocation" | "suspension";
|
|
13
|
+
export declare function createBitstring(numEntries?: number): Uint8Array;
|
|
14
|
+
/** Set/clear the status bit at `index`. Bits are MSB-first within each byte (per spec). */
|
|
15
|
+
export declare function setBit(bitstring: Uint8Array, index: number, value: boolean): void;
|
|
16
|
+
export declare function getBit(bitstring: Uint8Array, index: number): boolean;
|
|
17
|
+
/** `u` + base64url(GZIP(bitstring)) — the spec's `encodedList` multibase form. */
|
|
18
|
+
export declare function encodeBitstring(bitstring: Uint8Array): string;
|
|
19
|
+
export declare function decodeBitstring(encodedList: string): Uint8Array;
|
|
20
|
+
export interface BuildStatusListCredentialInput {
|
|
21
|
+
/** Stable URL where the status list VC is published. */
|
|
22
|
+
id: string;
|
|
23
|
+
issuer: string;
|
|
24
|
+
statusPurpose: StatusPurpose;
|
|
25
|
+
encodedList: string;
|
|
26
|
+
validFrom?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function buildStatusListCredential(input: BuildStatusListCredentialInput): JsonLdDocument;
|
|
29
|
+
/** Convenience: build + sign a status-list credential with the active key. */
|
|
30
|
+
export declare function signStatusListCredential(input: BuildStatusListCredentialInput, signOptions: SignOptions): SecuredDocument;
|
|
31
|
+
export interface CredentialStatusEntry {
|
|
32
|
+
id?: string;
|
|
33
|
+
type: string;
|
|
34
|
+
statusPurpose: string;
|
|
35
|
+
statusListIndex: string | number;
|
|
36
|
+
statusListCredential: string;
|
|
37
|
+
}
|
|
38
|
+
export interface RevocationCheck {
|
|
39
|
+
/** Whether revocation/suspension status was actually evaluated (false ⇒ offline + no status list). */
|
|
40
|
+
checked: boolean;
|
|
41
|
+
/** True iff the status bit is set (revoked OR suspended → not currently valid). */
|
|
42
|
+
revoked: boolean;
|
|
43
|
+
/** The status purpose that was evaluated, when checked. */
|
|
44
|
+
statusPurpose?: StatusPurpose;
|
|
45
|
+
reason?: string;
|
|
46
|
+
}
|
|
47
|
+
/** Normalize a VC `issuer` (a string or a `{ id }` object) to its string id, or undefined. */
|
|
48
|
+
export declare function issuerId(value: unknown): string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Evaluate the revocation bit OFFLINE: verify the status-list credential's own
|
|
51
|
+
* signature (same issuer key), bind it to the entry, then read the bit. Fails
|
|
52
|
+
* SAFE — any defect returns `checked:false` (status unknown), never a false
|
|
53
|
+
* "not revoked". `checked:false` is honest ("verifiable as of issuance; status
|
|
54
|
+
* not checked"), distinct from a confirmed `revoked:false`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function checkRevocationOffline(credentialStatus: CredentialStatusEntry | CredentialStatusEntry[] | undefined, statusListCredential: unknown, resolvePublicKey: PublicKeyResolver, credentialIssuer?: string): RevocationCheck;
|
|
57
|
+
//# sourceMappingURL=status-list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-list.d.ts","sourceRoot":"","sources":["../../src/annex-iv-verify/status-list.ts"],"names":[],"mappings":"AAkCA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AAE7B,yEAAyE;AACzE,eAAO,MAAM,0BAA0B,SAAS,CAAC;AACjD;;;GAGG;AACH,eAAO,MAAM,6BAA6B,QAAmB,CAAC;AAC9D,eAAO,MAAM,gCAAgC,MAAM,CAAC;AACpD,eAAO,MAAM,2BAA2B,6BAA6B,CAAC;AACtE,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAE3D,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,YAAY,CAAC;AAIxD,wBAAgB,eAAe,CAAC,UAAU,SAA6B,GAAG,UAAU,CAKnF;AAQD,2FAA2F;AAC3F,wBAAgB,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAMjF;AAED,wBAAgB,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAKpE;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,SAAS,EAAE,UAAU,GAAG,MAAM,CAE7D;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU,CAW/D;AAID,MAAM,WAAW,8BAA8B;IAC7C,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,8BAA8B,GAAG,cAAc,CAc/F;AAED,8EAA8E;AAC9E,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,8BAA8B,EACrC,WAAW,EAAE,WAAW,GACvB,eAAe,CAEjB;AAID,MAAM,WAAW,qBAAqB;IACpC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,sGAAsG;IACtG,OAAO,EAAE,OAAO,CAAC;IACjB,mFAAmF;IACnF,OAAO,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AASD,8FAA8F;AAC9F,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAM3D;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,gBAAgB,EAAE,qBAAqB,GAAG,qBAAqB,EAAE,GAAG,SAAS,EAC7E,oBAAoB,EAAE,OAAO,EAC7B,gBAAgB,EAAE,iBAAiB,EACnC,gBAAgB,CAAC,EAAE,MAAM,GACxB,eAAe,CA2DjB"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// VERIFY-only faithful port of `src/lib/crypto/status-list.ts` — the kernel is
|
|
2
|
+
// the source of truth; the golden cross-vectors (`__tests__/golden-vectors.json`)
|
|
3
|
+
// break first on any divergence. Do NOT edit semantics independently.
|
|
4
|
+
//
|
|
5
|
+
// Copied VERBATIM, with the ONLY change being the relative-import rewrite to an
|
|
6
|
+
// EXPLICIT `.js` extension (the SDK is `module:"Node16"`). The `node:zlib`
|
|
7
|
+
// import is unchanged — REQUIRED for byte-identity (incl. the gzip-bomb cap on
|
|
8
|
+
// the attacker-controlled `encodedList`); do NOT web-ify. The sign-side
|
|
9
|
+
// (`createBitstring`/`setBit`/`encodeBitstring`/`signStatusListCredential`) is
|
|
10
|
+
// dead-but-private — only `checkRevocationOffline` + `decodeBitstring` + `getBit`
|
|
11
|
+
// + `issuerId` + `asEntries` are verifier-reached.
|
|
12
|
+
//
|
|
13
|
+
// VERBATIM-COPY FENCE (do NOT narrow): the verify-side status-purpose predicate
|
|
14
|
+
// (the `|| e.statusPurpose === 'suspension'` arm), the `subject.statusPurpose ===
|
|
15
|
+
// entry.statusPurpose` equality, and `asEntries` (array handling + the `.find`
|
|
16
|
+
// skip-non-matching) are load-bearing — the golden vectors V10 (suspension) and
|
|
17
|
+
// V11 (multi-entry array) break first if any of them is pruned.
|
|
18
|
+
/**
|
|
19
|
+
* W3C **Bitstring Status List v1.0** — issuer-signed revocation infrastructure.
|
|
20
|
+
*
|
|
21
|
+
* Decision-RVK (ratified): ship the issuer-signed status list so offline
|
|
22
|
+
* `/verify` is a TWO-check contract — *signature-over-bytes valid AND status-bit
|
|
23
|
+
* == not-revoked*. A pure signature check returns VALID for a cryptographically
|
|
24
|
+
* intact file the issuer has since revoked; on a 10-yr Article-18 technical file
|
|
25
|
+
* that is a falsifiable "verifiable" claim. The status-list VC is signed with the
|
|
26
|
+
* SAME Ed25519 key + kid and may be fetched out-of-band / cached by the verifier.
|
|
27
|
+
*
|
|
28
|
+
* The revocation STATE source here is config/env (DB-free, consistent with the
|
|
29
|
+
* rest of W2.5a); wiring the bit population from live DB revocation state — and
|
|
30
|
+
* pinning `status_list_ref` + index into the signed `ANNEX-IV-BIND-v1` payload —
|
|
31
|
+
* is W2.
|
|
32
|
+
*/
|
|
33
|
+
import { gzipSync, gunzipSync } from "node:zlib";
|
|
34
|
+
import { bytesToBase64url, base64urlToBytes } from "./jwk.js";
|
|
35
|
+
import { signDocument, verifyDocument, } from "./data-integrity.js";
|
|
36
|
+
/** Minimum list length per spec: 16 KiB = 131,072 single-bit entries. */
|
|
37
|
+
export const DEFAULT_STATUS_LIST_LENGTH = 131072;
|
|
38
|
+
/**
|
|
39
|
+
* Hard cap on a DECODED status-list bitstring (10 MiB ≈ 83M entries). The offline
|
|
40
|
+
* verifier gunzips an attacker-supplied `encodedList`; this bounds a gzip bomb.
|
|
41
|
+
*/
|
|
42
|
+
export const MAX_DECODED_STATUS_LIST_BYTES = 10 * 1024 * 1024;
|
|
43
|
+
export const MULTIBASE_BASE64URL_NOPAD_PREFIX = "u";
|
|
44
|
+
export const BITSTRING_STATUS_LIST_ENTRY = "BitstringStatusListEntry";
|
|
45
|
+
export const BITSTRING_STATUS_LIST = "BitstringStatusList";
|
|
46
|
+
// ─── Bitstring ─────────────────────────────────────────────────────────────--
|
|
47
|
+
export function createBitstring(numEntries = DEFAULT_STATUS_LIST_LENGTH) {
|
|
48
|
+
if (numEntries <= 0 || numEntries % 8 !== 0) {
|
|
49
|
+
throw new Error("Bitstring length must be a positive multiple of 8");
|
|
50
|
+
}
|
|
51
|
+
return new Uint8Array(numEntries / 8);
|
|
52
|
+
}
|
|
53
|
+
function assertIndex(bitstring, index) {
|
|
54
|
+
if (!Number.isInteger(index) || index < 0 || index >= bitstring.length * 8) {
|
|
55
|
+
throw new Error(`Status index ${index} out of range`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Set/clear the status bit at `index`. Bits are MSB-first within each byte (per spec). */
|
|
59
|
+
export function setBit(bitstring, index, value) {
|
|
60
|
+
assertIndex(bitstring, index);
|
|
61
|
+
const byte = index >> 3;
|
|
62
|
+
const mask = 1 << (7 - (index & 7));
|
|
63
|
+
if (value)
|
|
64
|
+
bitstring[byte] |= mask;
|
|
65
|
+
else
|
|
66
|
+
bitstring[byte] &= ~mask;
|
|
67
|
+
}
|
|
68
|
+
export function getBit(bitstring, index) {
|
|
69
|
+
assertIndex(bitstring, index);
|
|
70
|
+
const byte = index >> 3;
|
|
71
|
+
const mask = 1 << (7 - (index & 7));
|
|
72
|
+
return (bitstring[byte] & mask) !== 0;
|
|
73
|
+
}
|
|
74
|
+
/** `u` + base64url(GZIP(bitstring)) — the spec's `encodedList` multibase form. */
|
|
75
|
+
export function encodeBitstring(bitstring) {
|
|
76
|
+
return MULTIBASE_BASE64URL_NOPAD_PREFIX + bytesToBase64url(gzipSync(bitstring));
|
|
77
|
+
}
|
|
78
|
+
export function decodeBitstring(encodedList) {
|
|
79
|
+
if (encodedList[0] !== MULTIBASE_BASE64URL_NOPAD_PREFIX) {
|
|
80
|
+
throw new Error("encodedList must be multibase base64url ('u' prefix)");
|
|
81
|
+
}
|
|
82
|
+
// `maxOutputLength` makes gunzip throw rather than expand a gzip bomb to
|
|
83
|
+
// exhaust memory — `encodedList` is attacker-controlled in the offline route.
|
|
84
|
+
return new Uint8Array(gunzipSync(base64urlToBytes(encodedList.slice(1)), {
|
|
85
|
+
maxOutputLength: MAX_DECODED_STATUS_LIST_BYTES,
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
export function buildStatusListCredential(input) {
|
|
89
|
+
return {
|
|
90
|
+
"@context": ["https://www.w3.org/ns/credentials/v2"],
|
|
91
|
+
id: input.id,
|
|
92
|
+
type: ["VerifiableCredential", "BitstringStatusListCredential"],
|
|
93
|
+
issuer: input.issuer,
|
|
94
|
+
validFrom: input.validFrom ?? new Date().toISOString().replace(/\.\d+Z$/, "Z"),
|
|
95
|
+
credentialSubject: {
|
|
96
|
+
id: `${input.id}#list`,
|
|
97
|
+
type: BITSTRING_STATUS_LIST,
|
|
98
|
+
statusPurpose: input.statusPurpose,
|
|
99
|
+
encodedList: input.encodedList,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Convenience: build + sign a status-list credential with the active key. */
|
|
104
|
+
export function signStatusListCredential(input, signOptions) {
|
|
105
|
+
return signDocument(buildStatusListCredential(input), signOptions);
|
|
106
|
+
}
|
|
107
|
+
function asEntries(credentialStatus) {
|
|
108
|
+
if (!credentialStatus)
|
|
109
|
+
return [];
|
|
110
|
+
return Array.isArray(credentialStatus) ? credentialStatus : [credentialStatus];
|
|
111
|
+
}
|
|
112
|
+
/** Normalize a VC `issuer` (a string or a `{ id }` object) to its string id, or undefined. */
|
|
113
|
+
export function issuerId(value) {
|
|
114
|
+
if (typeof value === "string")
|
|
115
|
+
return value;
|
|
116
|
+
if (value && typeof value === "object" && typeof value.id === "string") {
|
|
117
|
+
return value.id;
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Evaluate the revocation bit OFFLINE: verify the status-list credential's own
|
|
123
|
+
* signature (same issuer key), bind it to the entry, then read the bit. Fails
|
|
124
|
+
* SAFE — any defect returns `checked:false` (status unknown), never a false
|
|
125
|
+
* "not revoked". `checked:false` is honest ("verifiable as of issuance; status
|
|
126
|
+
* not checked"), distinct from a confirmed `revoked:false`.
|
|
127
|
+
*/
|
|
128
|
+
export function checkRevocationOffline(credentialStatus, statusListCredential, resolvePublicKey, credentialIssuer) {
|
|
129
|
+
const entry = asEntries(credentialStatus).find((e) => e?.type === BITSTRING_STATUS_LIST_ENTRY &&
|
|
130
|
+
(e.statusPurpose === "revocation" || e.statusPurpose === "suspension"));
|
|
131
|
+
if (!entry)
|
|
132
|
+
return { checked: false, revoked: false, reason: "no status entry" };
|
|
133
|
+
if (!statusListCredential || typeof statusListCredential !== "object") {
|
|
134
|
+
return { checked: false, revoked: false, reason: "status list not provided (offline)" };
|
|
135
|
+
}
|
|
136
|
+
const sig = verifyDocument(statusListCredential, resolvePublicKey);
|
|
137
|
+
if (!sig.verified) {
|
|
138
|
+
return { checked: false, revoked: false, reason: `status list signature invalid: ${sig.reason ?? "unknown"}` };
|
|
139
|
+
}
|
|
140
|
+
const list = statusListCredential;
|
|
141
|
+
if (!Array.isArray(list.type) || !list.type.includes("BitstringStatusListCredential")) {
|
|
142
|
+
return { checked: false, revoked: false, reason: "not a BitstringStatusListCredential" };
|
|
143
|
+
}
|
|
144
|
+
if (list.id !== entry.statusListCredential) {
|
|
145
|
+
return { checked: false, revoked: false, reason: "status list id does not match credentialStatus" };
|
|
146
|
+
}
|
|
147
|
+
// Defense-in-depth issuer↔issuer binding — an Attestry single-issuer policy, NOT a
|
|
148
|
+
// W3C requirement: the Bitstring Status List spec (§2.2) explicitly PERMITS a status
|
|
149
|
+
// list whose issuer differs from the credential's, and its Validate algorithm (§3.2)
|
|
150
|
+
// has no issuer-match step. We nonetheless refuse to consult a DIFFERENT issuer's list
|
|
151
|
+
// if cross-tenant / multi-issuer signing is ever introduced. Single-issuer today, so
|
|
152
|
+
// not currently reachable. Enforced only when the caller knows the credential's issuer
|
|
153
|
+
// (otherwise left to the prior checks).
|
|
154
|
+
if (credentialIssuer !== undefined && issuerId(list.issuer) !== credentialIssuer) {
|
|
155
|
+
return { checked: false, revoked: false, reason: "status list issuer does not match credential issuer" };
|
|
156
|
+
}
|
|
157
|
+
const subject = list.credentialSubject;
|
|
158
|
+
if (!subject || subject.type !== BITSTRING_STATUS_LIST) {
|
|
159
|
+
return { checked: false, revoked: false, reason: "not a BitstringStatusList" };
|
|
160
|
+
}
|
|
161
|
+
if (subject.statusPurpose !== entry.statusPurpose) {
|
|
162
|
+
return { checked: false, revoked: false, reason: "statusPurpose mismatch" };
|
|
163
|
+
}
|
|
164
|
+
let bitstring;
|
|
165
|
+
try {
|
|
166
|
+
bitstring = decodeBitstring(subject.encodedList);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return { checked: false, revoked: false, reason: "invalid encodedList" };
|
|
170
|
+
}
|
|
171
|
+
// Strict base-10 statusListIndex (W3C Bitstring Status List v1.0 §2.1): reject
|
|
172
|
+
// non-conformant values rather than letting Number() silently read bit 0 / a wrong bit.
|
|
173
|
+
const idxRaw = String(entry.statusListIndex).trim();
|
|
174
|
+
if (!/^(0|[1-9][0-9]*)$/.test(idxRaw)) {
|
|
175
|
+
return { checked: false, revoked: false, reason: "malformed statusListIndex" };
|
|
176
|
+
}
|
|
177
|
+
const index = parseInt(idxRaw, 10);
|
|
178
|
+
try {
|
|
179
|
+
return { checked: true, revoked: getBit(bitstring, index), statusPurpose: entry.statusPurpose };
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return { checked: false, revoked: false, reason: "status index out of range" };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=status-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-list.js","sourceRoot":"","sources":["../../src/annex-iv-verify/status-list.ts"],"names":[],"mappings":"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"}
|