@totemsdk/root-identity 1.0.4 → 1.0.6

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Totem SDK Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -83,6 +83,6 @@ const wallet = RootIdentityWallet.fromPhrase(userInput);
83
83
 
84
84
  ## See also
85
85
 
86
- - [`@totemsdk/core`](../core) — `createPerAddressTreeKey` used internally for each child
87
- - [`@totemsdk/wots-lease`](../wots-lease) — manage signing slot watermarks for each child address
88
- - [`@totemsdk/statechain`](../statechain) — transfer assets between addresses off-chain
86
+ - [`@totemsdk/core`](https://www.npmjs.com/package/@totemsdk/core) — `createPerAddressTreeKey` used internally for each child
87
+ - [`@totemsdk/wots-lease`](https://www.npmjs.com/package/@totemsdk/wots-lease) — manage signing slot watermarks for each child address
88
+ - [`@totemsdk/statechain`](https://www.npmjs.com/package/@totemsdk/statechain) — transfer assets between addresses off-chain
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * RootIdentityWallet — single root identity controlling up to 64 on-chain addresses.
3
4
  *
@@ -19,9 +20,11 @@
19
20
  * `getRootUses()` / `getChildUses(index)` after each signing operation and
20
21
  * restore them via `setRootUses()` / `setChildUses()` on the next session.
21
22
  */
22
- import { sha3_256 } from '@noble/hashes/sha3.js';
23
- import { createPerAddressTreeKey, serializeTreeSignature, verifySignatureDetailed, scriptFromWotsPk, scriptToAddress, bytesToHex, hexToBytes, phraseToSeed, generateSeedPhrase, validatePhrase, } from '@totemsdk/core';
24
- export const MAX_CHILD_COUNT = 64;
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.RootIdentityWallet = exports.MAX_CHILD_COUNT = void 0;
25
+ const sha3_js_1 = require("@noble/hashes/sha3.js");
26
+ const core_1 = require("@totemsdk/core");
27
+ exports.MAX_CHILD_COUNT = 64;
25
28
  /**
26
29
  * Canonical message prefix for ownership proofs.
27
30
  * Bump the version if the message schema ever changes.
@@ -31,11 +34,11 @@ const OWNERSHIP_OP = 'TOTEM_OWNERSHIP_PROOF_V1';
31
34
  // Helpers
32
35
  // ─────────────────────────────────────────────────────────────────────────────
33
36
  function hashMessage(message) {
34
- return sha3_256(new TextEncoder().encode(message));
37
+ return (0, sha3_js_1.sha3_256)(new TextEncoder().encode(message));
35
38
  }
36
39
  function addressFromPublicKeyBytes(pkBytes) {
37
- const script = scriptFromWotsPk(pkBytes);
38
- return scriptToAddress(script);
40
+ const script = (0, core_1.scriptFromWotsPk)(pkBytes);
41
+ return (0, core_1.scriptToAddress)(script);
39
42
  }
40
43
  /**
41
44
  * Build the deterministic ownership-proof message that the root key signs.
@@ -57,13 +60,13 @@ function buildOwnershipMessage(rootAddress, childPublicKeys, timestamp) {
57
60
  // ─────────────────────────────────────────────────────────────────────────────
58
61
  // RootIdentityWallet
59
62
  // ─────────────────────────────────────────────────────────────────────────────
60
- export class RootIdentityWallet {
63
+ class RootIdentityWallet {
61
64
  // ── Construction ────────────────────────────────────────────────────────────
62
65
  /**
63
66
  * @param baseSeed - 32-byte raw seed (use `RootIdentityWallet.fromPhrase` for mnemonic input)
64
67
  * @param childCount - Number of child addresses (1–64, default 64)
65
68
  */
66
- constructor(baseSeed, childCount = MAX_CHILD_COUNT) {
69
+ constructor(baseSeed, childCount = exports.MAX_CHILD_COUNT) {
67
70
  /** Cached TreeKey instances — created lazily to save memory. */
68
71
  this.treeKeyCache = new Map();
69
72
  /**
@@ -78,23 +81,23 @@ export class RootIdentityWallet {
78
81
  if (baseSeed.length !== 32) {
79
82
  throw new Error(`baseSeed must be exactly 32 bytes, got ${baseSeed.length}`);
80
83
  }
81
- if (childCount < 1 || childCount > MAX_CHILD_COUNT) {
82
- throw new Error(`childCount must be 1–${MAX_CHILD_COUNT}, got ${childCount}`);
84
+ if (childCount < 1 || childCount > exports.MAX_CHILD_COUNT) {
85
+ throw new Error(`childCount must be 1–${exports.MAX_CHILD_COUNT}, got ${childCount}`);
83
86
  }
84
87
  this.baseSeed = baseSeed;
85
88
  this.childCount = childCount;
86
89
  }
87
90
  /** Create a wallet from a Minima-compatible BIP39 seed phrase. */
88
- static fromPhrase(phrase, childCount = MAX_CHILD_COUNT) {
89
- return new RootIdentityWallet(phraseToSeed(phrase), childCount);
91
+ static fromPhrase(phrase, childCount = exports.MAX_CHILD_COUNT) {
92
+ return new RootIdentityWallet((0, core_1.phraseToSeed)(phrase), childCount);
90
93
  }
91
94
  /** Generate a new random Minima-compatible 24-word seed phrase. */
92
95
  static generatePhrase() {
93
- return generateSeedPhrase();
96
+ return (0, core_1.generateSeedPhrase)();
94
97
  }
95
98
  /** Validate a Minima-compatible seed phrase. */
96
99
  static validatePhrase(phrase) {
97
- return validatePhrase(phrase);
100
+ return (0, core_1.validatePhrase)(phrase);
98
101
  }
99
102
  // ── Address accessors ────────────────────────────────────────────────────────
100
103
  /** Minima address for the root key (slot 0). */
@@ -145,7 +148,7 @@ export class RootIdentityWallet {
145
148
  return {
146
149
  address: this.getRootAddress(),
147
150
  publicKey: this.getRootPublicKey(),
148
- signature: bytesToHex(serializeTreeSignature(sig)),
151
+ signature: (0, core_1.bytesToHex)((0, core_1.serializeTreeSignature)(sig)),
149
152
  message,
150
153
  };
151
154
  }
@@ -165,7 +168,7 @@ export class RootIdentityWallet {
165
168
  return {
166
169
  address: this.getChildAddress(index),
167
170
  publicKey: this.getChildPublicKey(index),
168
- signature: bytesToHex(serializeTreeSignature(sig)),
171
+ signature: (0, core_1.bytesToHex)((0, core_1.serializeTreeSignature)(sig)),
169
172
  message,
170
173
  };
171
174
  }
@@ -224,11 +227,11 @@ export class RootIdentityWallet {
224
227
  const canonicalMessage = buildOwnershipMessage(rootAddress, childPublicKeys, timestamp);
225
228
  if (rootProof.message !== canonicalMessage)
226
229
  return false;
227
- const sigResult = verifySignatureDetailed(rootAddress, canonicalMessage, rootProof.signature, rootPublicKey);
230
+ const sigResult = (0, core_1.verifySignatureDetailed)(rootAddress, canonicalMessage, rootProof.signature, rootPublicKey);
228
231
  if (!sigResult.valid)
229
232
  return false;
230
233
  for (let i = 0; i < childPublicKeys.length; i++) {
231
- const pkBytes = hexToBytes(childPublicKeys[i]);
234
+ const pkBytes = (0, core_1.hexToBytes)(childPublicKeys[i]);
232
235
  const derivedAddress = addressFromPublicKeyBytes(pkBytes);
233
236
  if (derivedAddress.toLowerCase() !== childAddresses[i].toLowerCase())
234
237
  return false;
@@ -344,11 +347,11 @@ export class RootIdentityWallet {
344
347
  const cached = this.treeKeyCache.get(slot);
345
348
  if (cached)
346
349
  return cached;
347
- const treeKey = createPerAddressTreeKey(this.baseSeed, slot);
350
+ const treeKey = (0, core_1.createPerAddressTreeKey)(this.baseSeed, slot);
348
351
  this.treeKeyCache.set(slot, treeKey);
349
352
  // Cache address and public key while we have the TreeKey
350
353
  const pkBytes = treeKey.getPublicKey();
351
- const pkHex = bytesToHex(pkBytes);
354
+ const pkHex = (0, core_1.bytesToHex)(pkBytes);
352
355
  this.pubKeyCache.set(slot, pkHex);
353
356
  this.addressCache.set(slot, addressFromPublicKeyBytes(pkBytes));
354
357
  return treeKey;
@@ -368,3 +371,4 @@ export class RootIdentityWallet {
368
371
  return this.addressCache.get(slot);
369
372
  }
370
373
  }
374
+ exports.RootIdentityWallet = RootIdentityWallet;
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * @module @totemsdk/root-identity
3
4
  *
@@ -10,4 +11,8 @@
10
11
  * - Privacy-preserving KYC (selective disclosure)
11
12
  * - NFT / token ownership linking
12
13
  */
13
- export { RootIdentityWallet, MAX_CHILD_COUNT } from './RootIdentityWallet.js';
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.MAX_CHILD_COUNT = exports.RootIdentityWallet = void 0;
16
+ var RootIdentityWallet_js_1 = require("./RootIdentityWallet.js");
17
+ Object.defineProperty(exports, "RootIdentityWallet", { enumerable: true, get: function () { return RootIdentityWallet_js_1.RootIdentityWallet; } });
18
+ Object.defineProperty(exports, "MAX_CHILD_COUNT", { enumerable: true, get: function () { return RootIdentityWallet_js_1.MAX_CHILD_COUNT; } });
package/dist/types.js CHANGED
@@ -1,4 +1,5 @@
1
+ "use strict";
1
2
  /**
2
3
  * Wire types for @totemsdk/root-identity
3
4
  */
4
- export {};
5
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "@totemsdk/root-identity",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Single root identity controlling up to 64 on-chain addresses — all cryptographically linked via full independent 3-level TreeKeys",
5
- "type": "module",
6
5
  "main": "dist/index.js",
7
6
  "types": "dist/index.d.ts",
8
7
  "exports": {
@@ -16,10 +15,11 @@
16
15
  },
17
16
  "files": [
18
17
  "dist",
19
- "src"
18
+ "README.md",
19
+ "LICENSE"
20
20
  ],
21
21
  "peerDependencies": {
22
- "@noble/hashes": ">=1.3.0",
22
+ "@noble/hashes": "^2.0.0",
23
23
  "@totemsdk/core": ">=1.0.1"
24
24
  },
25
25
  "peerDependenciesMeta": {
@@ -38,27 +38,40 @@
38
38
  "access": "public"
39
39
  },
40
40
  "devDependencies": {
41
- "@noble/hashes": "^1.3.0",
41
+ "@noble/hashes": "^2.2.0",
42
42
  "@types/jest": "^29.0.0",
43
43
  "@types/node": "^20.0.0",
44
44
  "jest": "^29.0.0",
45
45
  "ts-jest": "^29.0.0",
46
46
  "typescript": "^5.0.0",
47
- "@totemsdk/core": "1.0.9"
47
+ "@totemsdk/core": "1.0.10"
48
48
  },
49
49
  "keywords": [
50
50
  "totem",
51
+ "totemsdk",
51
52
  "minima",
52
53
  "blockchain",
54
+ "quantum-resistant",
53
55
  "wots",
56
+ "kissvm",
57
+ "utxo",
54
58
  "identity",
55
- "quantum-resistant",
59
+ "did",
56
60
  "chain-of-custody",
57
61
  "dao-attestation",
58
62
  "kyc"
59
63
  ],
60
- "author": "Totem",
64
+ "author": "Totem SDK",
61
65
  "license": "MIT",
66
+ "homepage": "https://totemsdk.com",
67
+ "bugs": {
68
+ "url": "https://github.com/MrGheek/axia-totem/issues"
69
+ },
70
+ "repository": {
71
+ "type": "git",
72
+ "url": "git+https://github.com/MrGheek/axia-totem.git",
73
+ "directory": "packages/totem-sdk/packages/root-identity"
74
+ },
62
75
  "scripts": {
63
76
  "build": "tsc",
64
77
  "clean": "rm -rf dist",
@@ -1,435 +0,0 @@
1
- /**
2
- * RootIdentityWallet — single root identity controlling up to 64 on-chain addresses.
3
- *
4
- * Architecture
5
- * ────────────
6
- * baseSeed (32 bytes)
7
- * │
8
- * ├── Slot 0 createPerAddressTreeKey(baseSeed, 0) ← ROOT (signs ownership proofs)
9
- * ├── Slot 1 createPerAddressTreeKey(baseSeed, 1) ← child 0
10
- * ├── Slot 2 createPerAddressTreeKey(baseSeed, 2) ← child 1
11
- * │ …
12
- * └── Slot 64 createPerAddressTreeKey(baseSeed, 64) ← child 63
13
- *
14
- * Each slot gets a full independent 3-level TreeKey (64³ = 262 144 one-time signatures).
15
- * Derivation matches Minima Wallet.java exactly:
16
- * SHA3-256( MiniData(baseSeed) ∥ MiniData(BigInteger(slot)) )
17
- *
18
- * Watermarks are tracked in memory. Callers that need persistence should read
19
- * `getRootUses()` / `getChildUses(index)` after each signing operation and
20
- * restore them via `setRootUses()` / `setChildUses()` on the next session.
21
- */
22
-
23
- import { sha3_256 } from '@noble/hashes/sha3.js';
24
- import {
25
- createPerAddressTreeKey,
26
- serializeTreeSignature,
27
- verifySignatureDetailed,
28
- scriptFromWotsPk,
29
- scriptToAddress,
30
- bytesToHex,
31
- hexToBytes,
32
- phraseToSeed,
33
- generateSeedPhrase,
34
- validatePhrase,
35
- type TreeKey,
36
- } from '@totemsdk/core';
37
- import type { WotsProof, OwnershipProof } from './types.js';
38
-
39
- export const MAX_CHILD_COUNT = 64;
40
-
41
- /**
42
- * Canonical message prefix for ownership proofs.
43
- * Bump the version if the message schema ever changes.
44
- */
45
- const OWNERSHIP_OP = 'TOTEM_OWNERSHIP_PROOF_V1';
46
-
47
- // ─────────────────────────────────────────────────────────────────────────────
48
- // Helpers
49
- // ─────────────────────────────────────────────────────────────────────────────
50
-
51
- function hashMessage(message: string): Uint8Array {
52
- return sha3_256(new TextEncoder().encode(message));
53
- }
54
-
55
- function addressFromPublicKeyBytes(pkBytes: Uint8Array): string {
56
- const script = scriptFromWotsPk(pkBytes);
57
- return scriptToAddress(script);
58
- }
59
-
60
- /**
61
- * Build the deterministic ownership-proof message that the root key signs.
62
- *
63
- * Child public keys are sorted lexicographically before serialization so that
64
- * the same child set always produces the same canonical message regardless of
65
- * the order in which indices were passed to `proveOwnership`.
66
- *
67
- * Field order is intentional — do not reorder without bumping OWNERSHIP_OP.
68
- */
69
- function buildOwnershipMessage(
70
- rootAddress: string,
71
- childPublicKeys: string[],
72
- timestamp: string
73
- ): string {
74
- return JSON.stringify({
75
- op: OWNERSHIP_OP,
76
- rootAddress,
77
- childPublicKeys: [...childPublicKeys].sort(),
78
- timestamp,
79
- });
80
- }
81
-
82
- // ─────────────────────────────────────────────────────────────────────────────
83
- // RootIdentityWallet
84
- // ─────────────────────────────────────────────────────────────────────────────
85
-
86
- export class RootIdentityWallet {
87
- private readonly baseSeed: Uint8Array;
88
- private readonly childCount: number;
89
-
90
- /** Cached TreeKey instances — created lazily to save memory. */
91
- private readonly treeKeyCache: Map<number, TreeKey> = new Map();
92
-
93
- /**
94
- * Cached addresses and public keys per slot (slot 0 = root, slot i+1 = child i).
95
- * Avoids expensive TreeKey regeneration on repeated address lookups.
96
- */
97
- private readonly addressCache: Map<number, string> = new Map();
98
- private readonly pubKeyCache: Map<number, string> = new Map();
99
-
100
- /** Per-slot watermarks. */
101
- private rootUses: number = 0;
102
- private readonly childUsesMap: Map<number, number> = new Map();
103
-
104
- // ── Construction ────────────────────────────────────────────────────────────
105
-
106
- /**
107
- * @param baseSeed - 32-byte raw seed (use `RootIdentityWallet.fromPhrase` for mnemonic input)
108
- * @param childCount - Number of child addresses (1–64, default 64)
109
- */
110
- constructor(baseSeed: Uint8Array, childCount: number = MAX_CHILD_COUNT) {
111
- if (baseSeed.length !== 32) {
112
- throw new Error(`baseSeed must be exactly 32 bytes, got ${baseSeed.length}`);
113
- }
114
- if (childCount < 1 || childCount > MAX_CHILD_COUNT) {
115
- throw new Error(`childCount must be 1–${MAX_CHILD_COUNT}, got ${childCount}`);
116
- }
117
- this.baseSeed = baseSeed;
118
- this.childCount = childCount;
119
- }
120
-
121
- /** Create a wallet from a Minima-compatible BIP39 seed phrase. */
122
- static fromPhrase(phrase: string, childCount: number = MAX_CHILD_COUNT): RootIdentityWallet {
123
- return new RootIdentityWallet(phraseToSeed(phrase), childCount);
124
- }
125
-
126
- /** Generate a new random Minima-compatible 24-word seed phrase. */
127
- static generatePhrase(): string {
128
- return generateSeedPhrase();
129
- }
130
-
131
- /** Validate a Minima-compatible seed phrase. */
132
- static validatePhrase(phrase: string): boolean {
133
- return validatePhrase(phrase);
134
- }
135
-
136
- // ── Address accessors ────────────────────────────────────────────────────────
137
-
138
- /** Minima address for the root key (slot 0). */
139
- getRootAddress(): string {
140
- return this.slotAddress(0);
141
- }
142
-
143
- /** 64-char hex public key for the root key. */
144
- getRootPublicKey(): string {
145
- return this.slotPublicKey(0);
146
- }
147
-
148
- /** Minima address for child `index` (0-based). */
149
- getChildAddress(index: number): string {
150
- this.assertChildIndex(index);
151
- return this.slotAddress(index + 1);
152
- }
153
-
154
- /** 64-char hex public key for child `index`. */
155
- getChildPublicKey(index: number): string {
156
- this.assertChildIndex(index);
157
- return this.slotPublicKey(index + 1);
158
- }
159
-
160
- /** All addresses: root first, then all children in order. */
161
- getAllAddresses(): string[] {
162
- const out: string[] = [this.getRootAddress()];
163
- for (let i = 0; i < this.childCount; i++) {
164
- out.push(this.getChildAddress(i));
165
- }
166
- return out;
167
- }
168
-
169
- /** Root address and children as a structured object. */
170
- getAddressMap(): { root: string; children: string[] } {
171
- const children: string[] = [];
172
- for (let i = 0; i < this.childCount; i++) {
173
- children.push(this.getChildAddress(i));
174
- }
175
- return { root: this.getRootAddress(), children };
176
- }
177
-
178
- // ── Signing ─────────────────────────────────────────────────────────────────
179
-
180
- /**
181
- * Sign `message` with the root key.
182
- * Hashes the message with SHA3-256 before signing — identical to `verifySignatureDetailed`.
183
- * @throws if root TreeKey is exhausted (262 144 uses)
184
- */
185
- signFromRoot(message: string): WotsProof {
186
- const treeKey = this.getOrCreateTreeKey(0);
187
- treeKey.setUses(this.rootUses);
188
- const sig = treeKey.sign(hashMessage(message));
189
- this.rootUses++;
190
- return {
191
- address: this.getRootAddress(),
192
- publicKey: this.getRootPublicKey(),
193
- signature: bytesToHex(serializeTreeSignature(sig)),
194
- message,
195
- };
196
- }
197
-
198
- /**
199
- * Sign `message` with child key `index` (0-based).
200
- * Each child maintains its own independent use counter.
201
- * @throws if child TreeKey is exhausted or index out of range
202
- */
203
- signFromChild(index: number, message: string): WotsProof {
204
- this.assertChildIndex(index);
205
- const slot = index + 1;
206
- const treeKey = this.getOrCreateTreeKey(slot);
207
- const uses = this.childUsesMap.get(index) ?? 0;
208
- treeKey.setUses(uses);
209
- const sig = treeKey.sign(hashMessage(message));
210
- this.childUsesMap.set(index, uses + 1);
211
- return {
212
- address: this.getChildAddress(index),
213
- publicKey: this.getChildPublicKey(index),
214
- signature: bytesToHex(serializeTreeSignature(sig)),
215
- message,
216
- };
217
- }
218
-
219
- // ── Ownership proofs ─────────────────────────────────────────────────────────
220
-
221
- /**
222
- * Produce an ownership proof demonstrating that the root key controls all
223
- * given child addresses.
224
- *
225
- * @param childIndices - Which children to include (0-based, order preserved)
226
- */
227
- proveOwnership(childIndices: number[]): OwnershipProof {
228
- if (childIndices.length === 0) {
229
- throw new Error('childIndices must contain at least one index');
230
- }
231
- const seen = new Set<number>();
232
- for (const idx of childIndices) {
233
- this.assertChildIndex(idx);
234
- if (seen.has(idx)) throw new Error(`Duplicate child index: ${idx}`);
235
- seen.add(idx);
236
- }
237
-
238
- const timestamp = new Date().toISOString();
239
- const childAddresses = childIndices.map(i => this.getChildAddress(i));
240
- const childPublicKeys = childIndices.map(i => this.getChildPublicKey(i));
241
- const rootAddress = this.getRootAddress();
242
- const rootPublicKey = this.getRootPublicKey();
243
-
244
- const message = buildOwnershipMessage(rootAddress, childPublicKeys, timestamp);
245
- const rootProof = this.signFromRoot(message);
246
-
247
- return { rootAddress, rootPublicKey, childAddresses, childPublicKeys, rootProof, timestamp };
248
- }
249
-
250
- /**
251
- * Verify an ownership proof produced by `proveOwnership`.
252
- *
253
- * Returns `true` only when:
254
- * - The canonical message is reconstructed correctly (child keys are sorted).
255
- * - The root WOTS signature validates against the root public key and address.
256
- * - Every child public key correctly derives the corresponding child address.
257
- *
258
- * Pure crypto — no network access required.
259
- * Always returns `false` (never throws) on malformed or incomplete proof data.
260
- */
261
- static verifyOwnershipProof(proof: OwnershipProof): boolean {
262
- try {
263
- if (!proof || typeof proof !== 'object') return false;
264
-
265
- const { rootAddress, rootPublicKey, childAddresses, childPublicKeys, rootProof, timestamp } = proof;
266
-
267
- if (
268
- !rootAddress || !rootPublicKey || !timestamp ||
269
- !Array.isArray(childAddresses) || !Array.isArray(childPublicKeys) ||
270
- !rootProof || typeof rootProof.signature !== 'string'
271
- ) {
272
- return false;
273
- }
274
-
275
- if (childAddresses.length === 0) return false;
276
- if (childAddresses.length !== childPublicKeys.length) return false;
277
-
278
- const canonicalMessage = buildOwnershipMessage(rootAddress, childPublicKeys, timestamp);
279
- if (rootProof.message !== canonicalMessage) return false;
280
-
281
- const sigResult = verifySignatureDetailed(rootAddress, canonicalMessage, rootProof.signature, rootPublicKey);
282
- if (!sigResult.valid) return false;
283
-
284
- for (let i = 0; i < childPublicKeys.length; i++) {
285
- const pkBytes = hexToBytes(childPublicKeys[i]);
286
- const derivedAddress = addressFromPublicKeyBytes(pkBytes);
287
- if (derivedAddress.toLowerCase() !== childAddresses[i].toLowerCase()) return false;
288
- }
289
-
290
- return true;
291
- } catch {
292
- return false;
293
- }
294
- }
295
-
296
- // ── TreeKey access (for low-level transaction signing) ───────────────────────
297
-
298
- /**
299
- * Get (or create and cache) the TreeKey for child `index` (0-based).
300
- *
301
- * Callers that need positional watermark control (e.g. `setUses(l1*64+l2)`)
302
- * can use this to drive WOTS signing directly — the same path the Totem
303
- * extension uses for transaction signing in RootTree mode.
304
- *
305
- * Note: This does NOT advance the high-level `childUsesMap` counter.
306
- * Call `getChildUses` / `setChildUses` yourself if you need to track uses.
307
- */
308
- getChildTreeKey(index: number): TreeKey {
309
- this.assertChildIndex(index);
310
- return this.getOrCreateTreeKey(index + 1);
311
- }
312
-
313
- /**
314
- * Get (or create and cache) the root TreeKey (slot 0).
315
- *
316
- * Useful for callers that need direct access to the root key's TreeKey
317
- * object for custom signing operations.
318
- */
319
- getRootTreeKey(): TreeKey {
320
- return this.getOrCreateTreeKey(0);
321
- }
322
-
323
- // ── Watermark access ─────────────────────────────────────────────────────────
324
-
325
- /** Number of times the root key has been used (for persistence). */
326
- getRootUses(): number { return this.rootUses; }
327
-
328
- /** Restore root watermark from a previously persisted value. */
329
- setRootUses(uses: number): void {
330
- if (uses < 0) throw new Error('uses must be non-negative');
331
- this.rootUses = uses;
332
- }
333
-
334
- /** Number of times child `index` has signed (for persistence). */
335
- getChildUses(index: number): number {
336
- this.assertChildIndex(index);
337
- return this.childUsesMap.get(index) ?? 0;
338
- }
339
-
340
- /** Restore child watermark from a previously persisted value. */
341
- setChildUses(index: number, uses: number): void {
342
- this.assertChildIndex(index);
343
- if (uses < 0) throw new Error('uses must be non-negative');
344
- this.childUsesMap.set(index, uses);
345
- }
346
-
347
- /** Number of child addresses configured for this wallet. */
348
- getChildCount(): number { return this.childCount; }
349
-
350
- /** Maximum one-time signatures available per slot (3 levels × 64 keys). */
351
- getMaxUsesPerSlot(): number { return 64 * 64 * 64; }
352
-
353
- /**
354
- * Return a serialisable snapshot of all current watermark counters.
355
- *
356
- * Persist the returned object (e.g. to encrypted storage) and pass it back
357
- * to `restoreWatermarkState()` at the start of the next session so that no
358
- * one-time-use signing slot is ever reused.
359
- *
360
- * @example
361
- * const state = wallet.getWatermarkState();
362
- * await storage.set('watermarks', JSON.stringify(state));
363
- */
364
- getWatermarkState(): { rootUses: number; childUses: Record<number, number> } {
365
- const childUses: Record<number, number> = {};
366
- for (const [index, uses] of this.childUsesMap) {
367
- childUses[index] = uses;
368
- }
369
- return { rootUses: this.rootUses, childUses };
370
- }
371
-
372
- /**
373
- * Restore watermark counters from a previously persisted snapshot.
374
- *
375
- * Call this immediately after constructing the wallet to prevent slot reuse
376
- * across sessions. All values must be non-negative integers; out-of-range
377
- * or invalid entries are silently skipped.
378
- *
379
- * @example
380
- * const saved = JSON.parse(await storage.get('watermarks') ?? '{}');
381
- * wallet.restoreWatermarkState(saved);
382
- */
383
- restoreWatermarkState(state: { rootUses?: number; childUses?: Record<number, number> }): void {
384
- if (typeof state?.rootUses === 'number' && state.rootUses >= 0) {
385
- this.rootUses = Math.floor(state.rootUses);
386
- }
387
- if (state?.childUses && typeof state.childUses === 'object') {
388
- for (const [key, uses] of Object.entries(state.childUses)) {
389
- const index = Number(key);
390
- if (Number.isInteger(index) && index >= 0 && index < this.childCount && typeof uses === 'number' && uses >= 0) {
391
- this.childUsesMap.set(index, Math.floor(uses));
392
- }
393
- }
394
- }
395
- }
396
-
397
- // ── Private helpers ──────────────────────────────────────────────────────────
398
-
399
- private assertChildIndex(index: number): void {
400
- if (!Number.isInteger(index) || index < 0 || index >= this.childCount) {
401
- throw new Error(`Child index ${index} is out of range [0, ${this.childCount - 1}]`);
402
- }
403
- }
404
-
405
- /**
406
- * Get or create the TreeKey for a given slot.
407
- * Also populates address and public key caches on first access.
408
- */
409
- private getOrCreateTreeKey(slot: number): TreeKey {
410
- const cached = this.treeKeyCache.get(slot);
411
- if (cached) return cached;
412
- const treeKey = createPerAddressTreeKey(this.baseSeed, slot);
413
- this.treeKeyCache.set(slot, treeKey);
414
- // Cache address and public key while we have the TreeKey
415
- const pkBytes = treeKey.getPublicKey();
416
- const pkHex = bytesToHex(pkBytes);
417
- this.pubKeyCache.set(slot, pkHex);
418
- this.addressCache.set(slot, addressFromPublicKeyBytes(pkBytes));
419
- return treeKey;
420
- }
421
-
422
- private slotPublicKey(slot: number): string {
423
- const cached = this.pubKeyCache.get(slot);
424
- if (cached) return cached;
425
- this.getOrCreateTreeKey(slot); // populates both caches
426
- return this.pubKeyCache.get(slot)!;
427
- }
428
-
429
- private slotAddress(slot: number): string {
430
- const cached = this.addressCache.get(slot);
431
- if (cached) return cached;
432
- this.getOrCreateTreeKey(slot); // populates both caches
433
- return this.addressCache.get(slot)!;
434
- }
435
- }
@@ -1,83 +0,0 @@
1
- /**
2
- * RootIdentityWallet watermark persistence tests
3
- *
4
- * Verifies that getWatermarkState() / restoreWatermarkState() correctly
5
- * snapshot and restore per-slot counters across simulated sessions.
6
- */
7
-
8
- import { RootIdentityWallet } from '../RootIdentityWallet.js';
9
-
10
- const SEED_32 = new Uint8Array(32).fill(0xab);
11
-
12
- describe('RootIdentityWallet — getWatermarkState / restoreWatermarkState', () => {
13
- it('initial state has rootUses=0 and empty childUses', () => {
14
- const wallet = new RootIdentityWallet(SEED_32, 4);
15
- const state = wallet.getWatermarkState();
16
- expect(state.rootUses).toBe(0);
17
- expect(Object.keys(state.childUses)).toHaveLength(0);
18
- });
19
-
20
- it('records root uses after signing', () => {
21
- const wallet = new RootIdentityWallet(SEED_32, 4);
22
- wallet.signFromRoot('hello');
23
- wallet.signFromRoot('world');
24
- const state = wallet.getWatermarkState();
25
- expect(state.rootUses).toBe(2);
26
- });
27
-
28
- it('records child uses per index independently', () => {
29
- const wallet = new RootIdentityWallet(SEED_32, 4);
30
- wallet.signFromChild(0, 'msg-a');
31
- wallet.signFromChild(2, 'msg-b');
32
- wallet.signFromChild(2, 'msg-c');
33
- const state = wallet.getWatermarkState();
34
- expect(state.childUses[0]).toBe(1);
35
- expect(state.childUses[1]).toBeUndefined();
36
- expect(state.childUses[2]).toBe(2);
37
- expect(state.childUses[3]).toBeUndefined();
38
- });
39
-
40
- it('restores watermarks across session boundary', () => {
41
- const wallet1 = new RootIdentityWallet(SEED_32, 4);
42
- wallet1.signFromRoot('r1');
43
- wallet1.signFromRoot('r2');
44
- wallet1.signFromChild(1, 'c1');
45
- wallet1.signFromChild(1, 'c2');
46
- wallet1.signFromChild(1, 'c3');
47
- const snapshot = wallet1.getWatermarkState();
48
-
49
- const wallet2 = new RootIdentityWallet(SEED_32, 4);
50
- wallet2.restoreWatermarkState(snapshot);
51
-
52
- expect(wallet2.getRootUses()).toBe(2);
53
- expect(wallet2.getChildUses(1)).toBe(3);
54
- expect(wallet2.getChildUses(0)).toBe(0);
55
- });
56
-
57
- it('restoreWatermarkState ignores out-of-range child indices', () => {
58
- const wallet = new RootIdentityWallet(SEED_32, 2);
59
- wallet.restoreWatermarkState({ rootUses: 5, childUses: { 0: 3, 99: 7, 1: 1 } });
60
- expect(wallet.getRootUses()).toBe(5);
61
- expect(wallet.getChildUses(0)).toBe(3);
62
- expect(wallet.getChildUses(1)).toBe(1);
63
- });
64
-
65
- it('restoreWatermarkState ignores negative values', () => {
66
- const wallet = new RootIdentityWallet(SEED_32, 2);
67
- wallet.restoreWatermarkState({ rootUses: -1, childUses: { 0: -5 } });
68
- expect(wallet.getRootUses()).toBe(0);
69
- expect(wallet.getChildUses(0)).toBe(0);
70
- });
71
-
72
- it('round-trips through JSON serialization', () => {
73
- const wallet1 = new RootIdentityWallet(SEED_32, 4);
74
- wallet1.signFromRoot('x');
75
- wallet1.signFromChild(3, 'y');
76
- const json = JSON.stringify(wallet1.getWatermarkState());
77
-
78
- const wallet2 = new RootIdentityWallet(SEED_32, 4);
79
- wallet2.restoreWatermarkState(JSON.parse(json));
80
- expect(wallet2.getRootUses()).toBe(1);
81
- expect(wallet2.getChildUses(3)).toBe(1);
82
- });
83
- });
package/src/index.ts DELETED
@@ -1,15 +0,0 @@
1
- /**
2
- * @module @totemsdk/root-identity
3
- *
4
- * Single root identity controlling up to 64 on-chain Minima addresses,
5
- * all cryptographically linked via full independent 3-level WOTS TreeKeys.
6
- *
7
- * Use cases:
8
- * - Chain-of-custody proofs
9
- * - DAO multi-address attestation
10
- * - Privacy-preserving KYC (selective disclosure)
11
- * - NFT / token ownership linking
12
- */
13
-
14
- export { RootIdentityWallet, MAX_CHILD_COUNT } from './RootIdentityWallet.js';
15
- export type { WotsProof, OwnershipProof } from './types.js';
package/src/types.ts DELETED
@@ -1,39 +0,0 @@
1
- /**
2
- * Wire types for @totemsdk/root-identity
3
- */
4
-
5
- /**
6
- * A single WOTS signing proof tied to an on-chain address.
7
- *
8
- * Both `signature` and `publicKey` are lower-case hex strings (no 0x prefix).
9
- * `message` is the exact UTF-8 string that was signed so callers can
10
- * reconstruct the SHA3-256 digest independently.
11
- */
12
- export interface WotsProof {
13
- address: string;
14
- publicKey: string;
15
- signature: string;
16
- message: string;
17
- }
18
-
19
- /**
20
- * Ownership proof demonstrating that a root key controls a set of child addresses.
21
- *
22
- * The root key signs a canonical JSON message that includes all child public keys
23
- * and a timestamp, allowing third parties to verify the claim without any
24
- * interaction with the blockchain.
25
- *
26
- * Verification steps:
27
- * 1. Rebuild the canonical message from `rootAddress`, `childPublicKeys`, and `timestamp`.
28
- * 2. Verify `rootProof.signature` over that message with `rootProof.publicKey`.
29
- * 3. For each `(childPublicKeys[i], childAddresses[i])` pair confirm the address
30
- * is correctly derived from the public key.
31
- */
32
- export interface OwnershipProof {
33
- rootAddress: string;
34
- rootPublicKey: string;
35
- childAddresses: string[];
36
- childPublicKeys: string[];
37
- rootProof: WotsProof;
38
- timestamp: string;
39
- }