signet-protocol 1.10.0 → 1.11.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/dist/bonds.d.ts +8 -3
- package/dist/bonds.d.ts.map +1 -1
- package/dist/bonds.js +17 -6
- package/dist/bonds.js.map +1 -1
- package/dist/bot-ownership.d.ts +61 -0
- package/dist/bot-ownership.d.ts.map +1 -0
- package/dist/bot-ownership.js +88 -0
- package/dist/bot-ownership.js.map +1 -0
- package/dist/crypto.d.ts +2 -0
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +4 -0
- package/dist/crypto.js.map +1 -1
- package/dist/experimental.d.ts +19 -0
- package/dist/experimental.d.ts.map +1 -0
- package/dist/experimental.js +16 -0
- package/dist/experimental.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/url-auth.d.ts.map +1 -1
- package/dist/url-auth.js +2 -1
- package/dist/url-auth.js.map +1 -1
- package/dist/utils.d.ts +46 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +127 -0
- package/dist/utils.js.map +1 -1
- package/dist/vault-checkpoint.d.ts +50 -0
- package/dist/vault-checkpoint.d.ts.map +1 -0
- package/dist/vault-checkpoint.js +84 -0
- package/dist/vault-checkpoint.js.map +1 -0
- package/dist/vault-keys.d.ts +19 -0
- package/dist/vault-keys.d.ts.map +1 -0
- package/dist/vault-keys.js +57 -0
- package/dist/vault-keys.js.map +1 -0
- package/dist/vault-recovery.d.ts +99 -0
- package/dist/vault-recovery.d.ts.map +1 -0
- package/dist/vault-recovery.js +305 -0
- package/dist/vault-recovery.js.map +1 -0
- package/dist/vault-relay.d.ts +29 -0
- package/dist/vault-relay.d.ts.map +1 -0
- package/dist/vault-relay.js +131 -0
- package/dist/vault-relay.js.map +1 -0
- package/package.json +10 -5
- package/src/bonds.ts +18 -6
- package/src/bot-ownership.ts +77 -0
- package/src/crypto.ts +5 -0
- package/src/experimental.ts +25 -0
- package/src/index.ts +4 -1
- package/src/types.ts +8 -2
- package/src/url-auth.ts +2 -1
- package/src/utils.ts +122 -0
- package/src/vault-checkpoint.ts +98 -0
- package/src/vault-keys.ts +59 -0
- package/src/vault-recovery.ts +284 -0
- package/src/vault-relay.ts +112 -0
package/dist/utils.js
CHANGED
|
@@ -18,4 +18,131 @@ export function constantTimeEqual(a, b) {
|
|
|
18
18
|
}
|
|
19
19
|
return diff === 0;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* One character class for text that is shown to people: C0 controls, DEL, C1
|
|
23
|
+
* controls, Arabic letter mark (U+061C), zero-width and LRM/RLM (U+200B-U+200F),
|
|
24
|
+
* line/paragraph separators and bidi embeddings/overrides (U+2028-U+202E), and
|
|
25
|
+
* bidi isolates (U+2066-U+2069). Not global: build a `g` copy to strip.
|
|
26
|
+
*/
|
|
27
|
+
export const UNSAFE_TEXT_CHARS = /[\u0000-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2066-\u2069]/u;
|
|
28
|
+
/**
|
|
29
|
+
* `UNSAFE_TEXT_CHARS` minus U+200D (zero-width joiner), which is not a bidi
|
|
30
|
+
* control and is needed for emoji sequences, plus other invisible/deprecated-
|
|
31
|
+
* format characters a label has no legitimate use for: soft hyphen (U+00AD),
|
|
32
|
+
* Mongolian vowel separator (U+180E), word joiner and the deprecated invisible
|
|
33
|
+
* math operators (U+2060-U+2064), and the BOM / zero width no-break space
|
|
34
|
+
* (U+FEFF). This is a fixed denylist only; it does not cover every Unicode
|
|
35
|
+
* "format" (Cf) or Default_Ignorable code point (variation selectors, emoji
|
|
36
|
+
* tag characters, Hangul fillers, Arabic number-sign marks, deprecated music
|
|
37
|
+
* format controls, ...). Use `isSafeLabel` to validate labels \u2014 it checks
|
|
38
|
+
* this denylist plus those two Unicode categories, carving out only the
|
|
39
|
+
* narrow, positionally-constrained exceptions emoji sequences require.
|
|
40
|
+
*/
|
|
41
|
+
export const UNSAFE_LABEL_CHARS = /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u180e\u200b\u200c\u200e\u200f\u2028-\u202e\u2060-\u2064\u2066-\u2069\ufeff]/u;
|
|
42
|
+
const CF_OR_DEFAULT_IGNORABLE = /[\p{Cf}\p{Default_Ignorable_Code_Point}]/u;
|
|
43
|
+
const EXTENDED_PICTOGRAPHIC = /\p{Extended_Pictographic}/u;
|
|
44
|
+
const KEYCAP_BASE = /[#*0-9]/;
|
|
45
|
+
// A single unpaired UTF-16 surrogate is not a valid code point. In `u`-mode a
|
|
46
|
+
// correctly paired surrogate combines into one astral code point first and
|
|
47
|
+
// never matches this class; only a lone (unpaired) half does.
|
|
48
|
+
const LONE_SURROGATE = /[\uD800-\uDFFF]/u;
|
|
49
|
+
const ZWJ = 0x200d;
|
|
50
|
+
const VS15 = 0xfe0e, VS16 = 0xfe0f;
|
|
51
|
+
const SKIN_TONE_MIN = 0x1f3fb, SKIN_TONE_MAX = 0x1f3ff;
|
|
52
|
+
const TAG_CHAR_MIN = 0xe0020, TAG_CHAR_MAX = 0xe007e, TAG_CANCEL = 0xe007f, FLAG_BASE = 0x1f3f4;
|
|
53
|
+
// RGI emoji tag sequences: only these three UK subdivision flags are
|
|
54
|
+
// well-formed. Each entry is the tag-character run (base and cancel tag
|
|
55
|
+
// excluded) spelling the ISO 3166-2 subdivision code in lowercase ASCII,
|
|
56
|
+
// shifted into the U+E0000 tag-character plane.
|
|
57
|
+
const RGI_FLAG_TAG_SEQUENCES = ['gbeng', 'gbsct', 'gbwls'].map(code => Array.from(code, ch => 0xe0000 + ch.codePointAt(0)));
|
|
58
|
+
/**
|
|
59
|
+
* Whether `codePoints[index]` is "an emoji element" for the zero-width-joiner
|
|
60
|
+
* rule below: an Extended_Pictographic code point itself, or U+FE0F / an
|
|
61
|
+
* emoji skin-tone modifier (U+1F3FB-1F3FF) that directly follows one.
|
|
62
|
+
*/
|
|
63
|
+
function endsInEmojiElement(codePoints, index) {
|
|
64
|
+
if (index < 0)
|
|
65
|
+
return false;
|
|
66
|
+
const ch = codePoints[index];
|
|
67
|
+
if (EXTENDED_PICTOGRAPHIC.test(ch))
|
|
68
|
+
return true;
|
|
69
|
+
const cp = ch.codePointAt(0);
|
|
70
|
+
if (cp !== VS16 && !(cp >= SKIN_TONE_MIN && cp <= SKIN_TONE_MAX))
|
|
71
|
+
return false;
|
|
72
|
+
const prev = codePoints[index - 1];
|
|
73
|
+
return prev !== undefined && EXTENDED_PICTOGRAPHIC.test(prev);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A label is unsafe if it contains a lone (unpaired) UTF-16 surrogate, any
|
|
77
|
+
* `UNSAFE_LABEL_CHARS` character, or any Unicode format (Cf) or
|
|
78
|
+
* Default_Ignorable_Code_Point code point, EXCEPT the narrow in-context uses
|
|
79
|
+
* emoji sequences need:
|
|
80
|
+
*
|
|
81
|
+
* 1. U+200D (ZWJ) only between two emoji elements: the code point before it
|
|
82
|
+
* ends in an emoji element (see `endsInEmojiElement`) and the code point
|
|
83
|
+
* after it is Extended_Pictographic. A leading, trailing or doubled ZWJ,
|
|
84
|
+
* or one between ordinary letters, is rejected.
|
|
85
|
+
* 2. U+FE0E / U+FE0F (text/emoji variation selectors) only directly after
|
|
86
|
+
* an Extended_Pictographic code point or a keycap base ([#*0-9]). Every
|
|
87
|
+
* other variation selector (U+FE00-FE0D, U+E0100-E01EF) is rejected
|
|
88
|
+
* everywhere.
|
|
89
|
+
* 3. Tag characters U+E0020-E007E only inside one of the three RGI UK
|
|
90
|
+
* subdivision emoji flag sequences: U+1F3F4 (waving black flag) followed
|
|
91
|
+
* by the exact tag-character spelling of "gbeng", "gbsct" or "gbwls"
|
|
92
|
+
* then the cancel tag U+E007F, with nothing else in between. Any other
|
|
93
|
+
* tag run — a different or misspelled subdivision code, extra or missing
|
|
94
|
+
* tag characters, a hidden payload, a missing/extra cancel tag, or tag
|
|
95
|
+
* characters with no U+1F3F4 base — is rejected, as is U+E0001 anywhere.
|
|
96
|
+
*
|
|
97
|
+
* Iterates by code point, not UTF-16 code unit.
|
|
98
|
+
*/
|
|
99
|
+
export function isSafeLabel(label) {
|
|
100
|
+
if (LONE_SURROGATE.test(label) || UNSAFE_LABEL_CHARS.test(label))
|
|
101
|
+
return false;
|
|
102
|
+
const codePoints = Array.from(label);
|
|
103
|
+
// Indices that are part of one of the three well-formed RGI subdivision
|
|
104
|
+
// flag tag sequences: the exact tag-character spelling plus its cancel tag,
|
|
105
|
+
// immediately following a U+1F3F4.
|
|
106
|
+
const reservedTagIndex = new Set();
|
|
107
|
+
for (let i = 0; i < codePoints.length; i++) {
|
|
108
|
+
if (codePoints[i].codePointAt(0) !== FLAG_BASE)
|
|
109
|
+
continue;
|
|
110
|
+
for (const seq of RGI_FLAG_TAG_SEQUENCES) {
|
|
111
|
+
const cancelIndex = i + 1 + seq.length;
|
|
112
|
+
if (cancelIndex >= codePoints.length)
|
|
113
|
+
continue;
|
|
114
|
+
const matchesSpelling = seq.every((cp, k) => codePoints[i + 1 + k].codePointAt(0) === cp);
|
|
115
|
+
if (matchesSpelling && codePoints[cancelIndex].codePointAt(0) === TAG_CANCEL) {
|
|
116
|
+
for (let k = i + 1; k <= cancelIndex; k++)
|
|
117
|
+
reservedTagIndex.add(k);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (let i = 0; i < codePoints.length; i++) {
|
|
123
|
+
const ch = codePoints[i];
|
|
124
|
+
if (!CF_OR_DEFAULT_IGNORABLE.test(ch))
|
|
125
|
+
continue;
|
|
126
|
+
const cp = ch.codePointAt(0);
|
|
127
|
+
if (cp === ZWJ) {
|
|
128
|
+
const next = codePoints[i + 1];
|
|
129
|
+
if (endsInEmojiElement(codePoints, i - 1) && next !== undefined && EXTENDED_PICTOGRAPHIC.test(next))
|
|
130
|
+
continue;
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
if (cp === VS15 || cp === VS16) {
|
|
134
|
+
const prev = codePoints[i - 1];
|
|
135
|
+
if (prev !== undefined && (EXTENDED_PICTOGRAPHIC.test(prev) || KEYCAP_BASE.test(prev)))
|
|
136
|
+
continue;
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if ((cp >= TAG_CHAR_MIN && cp <= TAG_CHAR_MAX) || cp === TAG_CANCEL) {
|
|
140
|
+
if (reservedTagIndex.has(i))
|
|
141
|
+
continue;
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
21
148
|
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,MAAM,UAAU,SAAS,CAAC,GAAe;IACvC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,CAAa,EAAE,CAAa;IAC5D,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC"}
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare const VAULT_EVENT_KIND = 30078;
|
|
2
|
+
export declare const MAX_VAULT_CHUNKS = 32;
|
|
3
|
+
export declare const MAX_VAULT_DEVICES = 16;
|
|
4
|
+
export declare const MAX_VAULT_CONTROL_BYTES: number;
|
|
5
|
+
export declare const MAX_VAULT_CHUNK_BYTES = 100000;
|
|
6
|
+
export interface VaultChunkRef {
|
|
7
|
+
/** Exact signed event ID, never a mutable latest-by-tag lookup. */
|
|
8
|
+
eventId: string;
|
|
9
|
+
author: string;
|
|
10
|
+
/** SHA-256 of the UTF-8 encrypted event content. */
|
|
11
|
+
contentHash: string;
|
|
12
|
+
contentBytes: number;
|
|
13
|
+
}
|
|
14
|
+
export interface VaultCheckpoint {
|
|
15
|
+
v: 1;
|
|
16
|
+
purpose: string;
|
|
17
|
+
rotation: number;
|
|
18
|
+
sequence: number;
|
|
19
|
+
/** SHA-256 of the complete plaintext, checked before accepting a restored copy. */
|
|
20
|
+
revision: string;
|
|
21
|
+
/** Device whose independently replaceable checkpoint this is. */
|
|
22
|
+
publisher?: string;
|
|
23
|
+
devicePubkeys: string[];
|
|
24
|
+
/** Ordered chunk references; empty datasets still have one encrypted chunk. */
|
|
25
|
+
chunks: VaultChunkRef[];
|
|
26
|
+
/** Optional pointer, exactly rotation + 1; recovery follows it from index zero. */
|
|
27
|
+
nextRotation?: number;
|
|
28
|
+
}
|
|
29
|
+
/** Publicly computable opaque tag; confidentiality comes from encryption and private keys. */
|
|
30
|
+
export declare function vaultCheckpointTag(author: string, publisher?: string): string;
|
|
31
|
+
/** The single d-tag of a checkpoint-shaped event, or null. Every checkpoint tag
|
|
32
|
+
* is `vaultCheckpointTag` output: 32 lowercase hex characters. Necessary, not
|
|
33
|
+
* sufficient: the publisher is encrypted, so the shape cannot prove the tag. */
|
|
34
|
+
export declare function vaultCheckpointShapedTag(event: {
|
|
35
|
+
tags?: unknown;
|
|
36
|
+
}): string | null;
|
|
37
|
+
export declare function vaultContentHash(content: string): string;
|
|
38
|
+
/** Accept no partial checkpoint. A bad reference must never silently lose data. */
|
|
39
|
+
export declare function parseVaultCheckpoint(raw: string, expected: {
|
|
40
|
+
purpose: string;
|
|
41
|
+
rotation: number;
|
|
42
|
+
}): VaultCheckpoint | null;
|
|
43
|
+
/** Integrity check before decrypting. Signature and event-ID verification is also required. */
|
|
44
|
+
export declare function matchesVaultChunk(ref: VaultChunkRef, event: {
|
|
45
|
+
id: string;
|
|
46
|
+
pubkey: string;
|
|
47
|
+
kind: number;
|
|
48
|
+
content: string;
|
|
49
|
+
}): boolean;
|
|
50
|
+
//# sourceMappingURL=vault-checkpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault-checkpoint.d.ts","sourceRoot":"","sources":["../src/vault-checkpoint.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,gBAAgB,QAAQ,CAAA;AACrC,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,iBAAiB,KAAK,CAAA;AACnC,eAAO,MAAM,uBAAuB,QAAY,CAAA;AAChD,eAAO,MAAM,qBAAqB,SAAU,CAAA;AAM5C,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,CAAC,EAAE,CAAC,CAAA;IACJ,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,mFAAmF;IACnF,QAAQ,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,+EAA+E;IAC/E,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,8FAA8F;AAC9F,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAI7E;AAID;;gFAEgF;AAChF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,GAAG,IAAI,CAKjF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,mFAAmF;AACnF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,eAAe,GAAG,IAAI,CAyBzH;AAED,+FAA+F;AAC/F,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAInI"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** Shared private-vault checkpoint contract. No relay I/O or application data schema. */
|
|
2
|
+
import { hmac } from '@noble/hashes/hmac.js';
|
|
3
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
4
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
|
|
5
|
+
import { parseVaultPurpose } from './vault-keys.js';
|
|
6
|
+
export const VAULT_EVENT_KIND = 30078;
|
|
7
|
+
export const MAX_VAULT_CHUNKS = 32;
|
|
8
|
+
export const MAX_VAULT_DEVICES = 16;
|
|
9
|
+
export const MAX_VAULT_CONTROL_BYTES = 12 * 1024;
|
|
10
|
+
export const MAX_VAULT_CHUNK_BYTES = 100_000;
|
|
11
|
+
const HEX = /^[0-9a-f]{64}$/;
|
|
12
|
+
const encoder = new TextEncoder();
|
|
13
|
+
const uint = (v) => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
|
|
14
|
+
const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
15
|
+
/** Publicly computable opaque tag; confidentiality comes from encryption and private keys. */
|
|
16
|
+
export function vaultCheckpointTag(author, publisher) {
|
|
17
|
+
if (!HEX.test(author))
|
|
18
|
+
throw new Error('Invalid vault author');
|
|
19
|
+
if (publisher !== undefined && !HEX.test(publisher))
|
|
20
|
+
throw new Error('Invalid vault publisher');
|
|
21
|
+
return bytesToHex(hmac(sha256, hexToBytes(author), encoder.encode(`signet-vault-route\u0000checkpoint\u0000${publisher ?? '0'}`))).slice(0, 32);
|
|
22
|
+
}
|
|
23
|
+
const CHECKPOINT_TAG = /^[0-9a-f]{32}$/;
|
|
24
|
+
/** The single d-tag of a checkpoint-shaped event, or null. Every checkpoint tag
|
|
25
|
+
* is `vaultCheckpointTag` output: 32 lowercase hex characters. Necessary, not
|
|
26
|
+
* sufficient: the publisher is encrypted, so the shape cannot prove the tag. */
|
|
27
|
+
export function vaultCheckpointShapedTag(event) {
|
|
28
|
+
if (!event || !Array.isArray(event.tags))
|
|
29
|
+
return null;
|
|
30
|
+
const dTags = event.tags.filter((t) => Array.isArray(t) && t[0] === 'd');
|
|
31
|
+
if (dTags.length !== 1 || dTags[0].length !== 2 || typeof dTags[0][1] !== 'string' || !CHECKPOINT_TAG.test(dTags[0][1]))
|
|
32
|
+
return null;
|
|
33
|
+
return dTags[0][1];
|
|
34
|
+
}
|
|
35
|
+
export function vaultContentHash(content) {
|
|
36
|
+
return bytesToHex(sha256(encoder.encode(content)));
|
|
37
|
+
}
|
|
38
|
+
/** Accept no partial checkpoint. A bad reference must never silently lose data. */
|
|
39
|
+
export function parseVaultCheckpoint(raw, expected) {
|
|
40
|
+
if (typeof raw !== 'string' || encoder.encode(raw).length > MAX_VAULT_CONTROL_BYTES)
|
|
41
|
+
return null;
|
|
42
|
+
let v;
|
|
43
|
+
try {
|
|
44
|
+
v = JSON.parse(raw);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (!object(v) || v.v !== 1 || typeof v.purpose !== 'string' || !parseVaultPurpose(v.purpose)
|
|
50
|
+
|| v.purpose !== expected.purpose || v.rotation !== expected.rotation || !uint(v.rotation) || v.rotation > 0xffff_ffff
|
|
51
|
+
|| !uint(v.sequence) || typeof v.revision !== 'string' || !HEX.test(v.revision))
|
|
52
|
+
return null;
|
|
53
|
+
if (!Array.isArray(v.devicePubkeys) || !v.devicePubkeys.length || v.devicePubkeys.length > MAX_VAULT_DEVICES
|
|
54
|
+
|| v.devicePubkeys.some(p => typeof p !== 'string' || !HEX.test(p))
|
|
55
|
+
|| new Set(v.devicePubkeys).size !== v.devicePubkeys.length)
|
|
56
|
+
return null;
|
|
57
|
+
if (v.publisher !== undefined && (typeof v.publisher !== 'string' || !v.devicePubkeys.includes(v.publisher)))
|
|
58
|
+
return null;
|
|
59
|
+
if (!Array.isArray(v.chunks) || !v.chunks.length || v.chunks.length > MAX_VAULT_CHUNKS)
|
|
60
|
+
return null;
|
|
61
|
+
const chunks = [];
|
|
62
|
+
for (const c of v.chunks) {
|
|
63
|
+
if (!object(c) || typeof c.eventId !== 'string' || !HEX.test(c.eventId)
|
|
64
|
+
|| typeof c.author !== 'string' || !v.devicePubkeys.includes(c.author)
|
|
65
|
+
|| typeof c.contentHash !== 'string' || !HEX.test(c.contentHash)
|
|
66
|
+
|| !uint(c.contentBytes) || c.contentBytes === 0 || c.contentBytes > MAX_VAULT_CHUNK_BYTES)
|
|
67
|
+
return null;
|
|
68
|
+
chunks.push({ eventId: c.eventId, author: c.author, contentHash: c.contentHash, contentBytes: c.contentBytes });
|
|
69
|
+
}
|
|
70
|
+
if (new Set(chunks.map(c => c.eventId)).size !== chunks.length)
|
|
71
|
+
return null;
|
|
72
|
+
if (v.nextRotation !== undefined && (v.nextRotation !== v.rotation + 1 || v.nextRotation > 0xffff_ffff))
|
|
73
|
+
return null;
|
|
74
|
+
return { v: 1, purpose: v.purpose, rotation: v.rotation, sequence: v.sequence,
|
|
75
|
+
revision: v.revision, ...(v.publisher === undefined ? {} : { publisher: v.publisher }), devicePubkeys: [...v.devicePubkeys], chunks,
|
|
76
|
+
...(v.nextRotation === undefined ? {} : { nextRotation: v.nextRotation }) };
|
|
77
|
+
}
|
|
78
|
+
/** Integrity check before decrypting. Signature and event-ID verification is also required. */
|
|
79
|
+
export function matchesVaultChunk(ref, event) {
|
|
80
|
+
return event.id === ref.eventId && event.pubkey === ref.author && event.kind === VAULT_EVENT_KIND
|
|
81
|
+
&& encoder.encode(event.content).length === ref.contentBytes
|
|
82
|
+
&& vaultContentHash(event.content) === ref.contentHash;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=vault-checkpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault-checkpoint.js","sourceRoot":"","sources":["../src/vault-checkpoint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Identity, TreeRoot } from 'nsec-tree';
|
|
2
|
+
export type VaultDataset = 'profiles' | 'contacts:owner' | 'contacts:bots' | 'credentials' | 'settings' | {
|
|
3
|
+
dependant: number;
|
|
4
|
+
};
|
|
5
|
+
export declare function vaultPurpose(dataset: VaultDataset): string;
|
|
6
|
+
/** Strict parser: aliases such as dependant-01 would derive a different key. */
|
|
7
|
+
export declare function parseVaultPurpose(purpose: string): VaultDataset | null;
|
|
8
|
+
/** Suitable for Heartwood's per-request context; never a registry persona. */
|
|
9
|
+
export declare function vaultKeyContext(dataset: VaultDataset, rotation?: number): {
|
|
10
|
+
purpose: string;
|
|
11
|
+
index: number;
|
|
12
|
+
};
|
|
13
|
+
/** Caller owns and must zeroise the returned child. The supplied root is retained. */
|
|
14
|
+
export declare function deriveVaultIdentity(root: TreeRoot, dataset: VaultDataset, rotation?: number): Identity;
|
|
15
|
+
/** Keep root construction and use in one module instance (roots are opaque).
|
|
16
|
+
* Caller must zeroise the returned child; the temporary root is always destroyed.
|
|
17
|
+
*/
|
|
18
|
+
export declare function vaultIdentityFromMnemonic(mnemonic: string, dataset: VaultDataset, rotation?: number): Identity;
|
|
19
|
+
//# sourceMappingURL=vault-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault-keys.d.ts","sourceRoot":"","sources":["../src/vault-keys.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAEnD,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,gBAAgB,GAAG,eAAe,GAAG,aAAa,GAAG,UAAU,GACnG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAA;AAWzB,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAO1D;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAQtE;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,SAAI,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAGvG;AAED,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,SAAI,GAAG,QAAQ,CAGjG;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,SAAI,GAAG,QAAQ,CAIzG"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Private dataset identities, derived directly from the tree root.
|
|
2
|
+
* Identity ordinals belong in the purpose; the derivation index is rotation.
|
|
3
|
+
* This does not change any existing persona derivation.
|
|
4
|
+
*/
|
|
5
|
+
import { derive, fromMnemonic } from 'nsec-tree';
|
|
6
|
+
const FIXED = new Set(['profiles', 'contacts:owner', 'contacts:bots', 'credentials', 'settings']);
|
|
7
|
+
const MAX_INDEX = 0xffff_ffff;
|
|
8
|
+
function ordinal(value) {
|
|
9
|
+
if (!Number.isInteger(value) || value < 0 || value > MAX_INDEX) {
|
|
10
|
+
throw new Error('Vault ordinal must be an unsigned 32-bit integer');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function vaultPurpose(dataset) {
|
|
14
|
+
if (typeof dataset === 'string' && FIXED.has(dataset))
|
|
15
|
+
return `signet:vault:${dataset}`;
|
|
16
|
+
if (dataset && typeof dataset === 'object' && 'dependant' in dataset) {
|
|
17
|
+
ordinal(dataset.dependant);
|
|
18
|
+
return `signet:vault:contacts:dependant-${dataset.dependant}`;
|
|
19
|
+
}
|
|
20
|
+
throw new Error('Unknown vault dataset');
|
|
21
|
+
}
|
|
22
|
+
/** Strict parser: aliases such as dependant-01 would derive a different key. */
|
|
23
|
+
export function parseVaultPurpose(purpose) {
|
|
24
|
+
if (typeof purpose !== 'string' || !purpose.startsWith('signet:vault:'))
|
|
25
|
+
return null;
|
|
26
|
+
const suffix = purpose.slice('signet:vault:'.length);
|
|
27
|
+
if (FIXED.has(suffix))
|
|
28
|
+
return suffix;
|
|
29
|
+
const match = /^contacts:dependant-(0|[1-9][0-9]*)$/.exec(suffix);
|
|
30
|
+
if (!match)
|
|
31
|
+
return null;
|
|
32
|
+
const dependant = Number(match[1]);
|
|
33
|
+
return Number.isInteger(dependant) && dependant <= MAX_INDEX ? { dependant } : null;
|
|
34
|
+
}
|
|
35
|
+
/** Suitable for Heartwood's per-request context; never a registry persona. */
|
|
36
|
+
export function vaultKeyContext(dataset, rotation = 0) {
|
|
37
|
+
ordinal(rotation);
|
|
38
|
+
return { purpose: vaultPurpose(dataset), index: rotation };
|
|
39
|
+
}
|
|
40
|
+
/** Caller owns and must zeroise the returned child. The supplied root is retained. */
|
|
41
|
+
export function deriveVaultIdentity(root, dataset, rotation = 0) {
|
|
42
|
+
const { purpose, index } = vaultKeyContext(dataset, rotation);
|
|
43
|
+
return derive(root, purpose, index);
|
|
44
|
+
}
|
|
45
|
+
/** Keep root construction and use in one module instance (roots are opaque).
|
|
46
|
+
* Caller must zeroise the returned child; the temporary root is always destroyed.
|
|
47
|
+
*/
|
|
48
|
+
export function vaultIdentityFromMnemonic(mnemonic, dataset, rotation = 0) {
|
|
49
|
+
const root = fromMnemonic(mnemonic);
|
|
50
|
+
try {
|
|
51
|
+
return deriveVaultIdentity(root, dataset, rotation);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
root.destroy();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=vault-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault-keys.js","sourceRoot":"","sources":["../src/vault-keys.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAMhD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,CAAC,CAAC,CAAA;AACjG,MAAM,SAAS,GAAG,WAAW,CAAA;AAE7B,SAAS,OAAO,CAAC,KAAa;IAC5B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAA;IACrE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAqB;IAChD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,gBAAgB,OAAO,EAAE,CAAA;IACvF,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,WAAW,IAAI,OAAO,EAAE,CAAC;QACrE,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;QAC1B,OAAO,mCAAmC,OAAO,CAAC,SAAS,EAAE,CAAA;IAC/D,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAA;AAC1C,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC;QAAE,OAAO,IAAI,CAAA;IACpF,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAA;IACpD,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO,MAAsB,CAAA;IACpD,MAAM,KAAK,GAAG,sCAAsC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACjE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA;IACvB,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAClC,OAAO,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACrF,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,eAAe,CAAC,OAAqB,EAAE,QAAQ,GAAG,CAAC;IACjE,OAAO,CAAC,QAAQ,CAAC,CAAA;IACjB,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;AAC5D,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,mBAAmB,CAAC,IAAc,EAAE,OAAqB,EAAE,QAAQ,GAAG,CAAC;IACrF,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;IAC7D,OAAO,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;AACrC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAAgB,EAAE,OAAqB,EAAE,QAAQ,GAAG,CAAC;IAC7F,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAA;IACnC,IAAI,CAAC;QAAC,OAAO,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAA;IAAC,CAAC;YACnD,CAAC;QAAC,IAAI,CAAC,OAAO,EAAE,CAAA;IAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { NostrEvent } from './types.js';
|
|
2
|
+
import type { VaultCheckpoint } from './vault-checkpoint.js';
|
|
3
|
+
/** A checkpoint dated further than this beyond the reader's clock is rejected,
|
|
4
|
+
* so one device with a bad clock cannot outrank every later honest head. */
|
|
5
|
+
export declare const VAULT_CLOCK_TOLERANCE_SECONDS = 300;
|
|
6
|
+
type Ready = Extract<VaultReadResult, {
|
|
7
|
+
state: 'ready';
|
|
8
|
+
}>;
|
|
9
|
+
export type VaultReadResult = {
|
|
10
|
+
state: 'absent';
|
|
11
|
+
}
|
|
12
|
+
/** `failedRelays`, when known, names the relays that did not answer. */
|
|
13
|
+
| {
|
|
14
|
+
state: 'unavailable';
|
|
15
|
+
failedRelays?: string[];
|
|
16
|
+
} | {
|
|
17
|
+
state: 'unusable';
|
|
18
|
+
reason: 'checkpoint' | 'rollback' | 'chunk' | 'revision';
|
|
19
|
+
} | {
|
|
20
|
+
state: 'ready';
|
|
21
|
+
plaintext: string;
|
|
22
|
+
checkpoint: VaultCheckpoint;
|
|
23
|
+
event: NostrEvent;
|
|
24
|
+
};
|
|
25
|
+
export interface VaultReader {
|
|
26
|
+
/** Must distinguish an empty reachable query from relay failure (throw).
|
|
27
|
+
* An error carrying `failedRelays: string[]` is reported in the result. */
|
|
28
|
+
checkpoints(author: string, dTag?: string): Promise<NostrEvent[]>;
|
|
29
|
+
/** Exact ID query. Missing data is a failed restore, never an empty dataset. */
|
|
30
|
+
chunk(eventId: string): Promise<NostrEvent | null>;
|
|
31
|
+
/** Open the v2 envelope only; sender is the vault key, not the chunk signer. */
|
|
32
|
+
open(content: string, vaultAuthor: string): Promise<string | null>;
|
|
33
|
+
}
|
|
34
|
+
/** Read one rotation. Callers may fall back to legacy ONLY on `absent`.
|
|
35
|
+
* Invalid, incomplete, offline and rolled-back backups are distinguishable from
|
|
36
|
+
* never-migrated accounts. No state is persisted or marked canonical here.
|
|
37
|
+
* `now` (unix seconds, default the local clock) bounds created_at from above.
|
|
38
|
+
*/
|
|
39
|
+
export declare function readVaultSnapshot(reader: VaultReader, expected: {
|
|
40
|
+
author: string;
|
|
41
|
+
purpose: string;
|
|
42
|
+
rotation: number;
|
|
43
|
+
minSequence?: number;
|
|
44
|
+
publisher?: string;
|
|
45
|
+
now?: number;
|
|
46
|
+
}): Promise<VaultReadResult>;
|
|
47
|
+
/** Read the newest rotation's legacy (publisher-less) checkpoint. Rotations are
|
|
48
|
+
* contiguous from zero; see `newestRotation` for the revocation boundary. A
|
|
49
|
+
* `nextRotation` pointer (always rotation + 1) to a rotation with no authentic
|
|
50
|
+
* checkpoint is damage, not a never-migrated account.
|
|
51
|
+
*
|
|
52
|
+
* Any key present in `floors`, whatever its value (including 0), counts as a
|
|
53
|
+
* rotation this caller has already reached: the walk refuses to land below
|
|
54
|
+
* the highest such key. Callers must not pre-fill a floor for a rotation they
|
|
55
|
+
* have not actually reached, or a legitimate read of that rotation would be
|
|
56
|
+
* refused as a rollback.
|
|
57
|
+
*/
|
|
58
|
+
export declare function readVaultRotations(resolve: (rotation: number) => Promise<{
|
|
59
|
+
reader: VaultReader;
|
|
60
|
+
author: string;
|
|
61
|
+
}>, purpose: string, floors?: Readonly<Record<number, number>>, now?: number): Promise<VaultReadResult>;
|
|
62
|
+
export type VaultHeadsResult = Exclude<VaultReadResult, {
|
|
63
|
+
state: 'ready';
|
|
64
|
+
}> | {
|
|
65
|
+
state: 'ready';
|
|
66
|
+
snapshots: Ready[];
|
|
67
|
+
};
|
|
68
|
+
interface HeadsExpected {
|
|
69
|
+
author: string;
|
|
70
|
+
purpose: string;
|
|
71
|
+
rotation: number;
|
|
72
|
+
sequenceFloors?: Readonly<Record<string, number>>;
|
|
73
|
+
now?: number;
|
|
74
|
+
}
|
|
75
|
+
/** Mergeable per-device heads prevent last-writer replacement across devices.
|
|
76
|
+
* Discovery queries only the private vault author. No device roster is public.
|
|
77
|
+
* Reads one rotation strictly: events without a checkpoint-shaped d-tag are
|
|
78
|
+
* ignored, then every remaining head must be valid, and more than 16 fails.
|
|
79
|
+
*/
|
|
80
|
+
export declare function readVaultHeads(reader: VaultReader, expected: HeadsExpected): Promise<VaultHeadsResult>;
|
|
81
|
+
/** Read the newest rotation's merged per-device heads. Rotation is a
|
|
82
|
+
* revocation boundary (see `newestRotation`): heads of a superseded rotation
|
|
83
|
+
* are never merged, opened or counted, so the rotating writer must carry every
|
|
84
|
+
* head's state into the new rotation before publishing there. Floors from
|
|
85
|
+
* `resolve` apply to the rotation that is read.
|
|
86
|
+
*
|
|
87
|
+
* `minRotation` is the highest rotation this caller has already reached. If
|
|
88
|
+
* every relay withholds the true newest rotation's successor, `newestRotation`
|
|
89
|
+
* cannot distinguish that from a vault that never rotated further, and would
|
|
90
|
+
* otherwise report a lower rotation's heads as `ready`; a caller-tracked floor
|
|
91
|
+
* on the walk itself is what catches that withholding.
|
|
92
|
+
*/
|
|
93
|
+
export declare function readVaultHeadRotations(resolve: (rotation: number) => Promise<{
|
|
94
|
+
reader: VaultReader;
|
|
95
|
+
author: string;
|
|
96
|
+
sequenceFloors?: Readonly<Record<string, number>>;
|
|
97
|
+
}>, purpose: string, now?: number, minRotation?: number): Promise<VaultHeadsResult>;
|
|
98
|
+
export {};
|
|
99
|
+
//# sourceMappingURL=vault-recovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault-recovery.d.ts","sourceRoot":"","sources":["../src/vault-recovery.ts"],"names":[],"mappings":"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"}
|