@totemsdk/core 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # @totemsdk/core
2
+
3
+ **The cryptographic engine — every other package depends on this.**
4
+
5
+ Zero production dependencies (only `@noble/hashes` as a peer). Provides quantum-resistant WOTS signatures, hierarchical TreeKey address derivation, BIP39 seed phrases, Merkle Mountain Range proofs, and byte-exact Minima Java-compatible transaction serialization.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @totemsdk/core @noble/hashes
11
+ ```
12
+
13
+ ## What's inside
14
+
15
+ | Module | What it does |
16
+ |--------|-------------|
17
+ | **WOTS** | `wotsKeypairFromSeed`, `wotsSign`, `wotsVerify`, `wotsPkFromSig` — quantum-resistant Winternitz One-Time Signatures |
18
+ | **TreeKey** | `createPerAddressTreeKey`, `verifyTreeSignature` — 3-level hierarchical signing trees; one seed → many signing addresses |
19
+ | **BIP39** | `generateSeedPhrase`, `validatePhrase`, `phraseToSeed` — 24-word mnemonic generation and recovery |
20
+ | **MMR** | Full Merkle Mountain Range proof construction and verification for Minima's UTXO set |
21
+ | **Serialization** | `serializeCoin`, `serializeTransaction`, `computeTransactionDigest`, `buildMinimaCoin` — byte-identical to the Minima Java node |
22
+ | **Verification** | `verifySignatureDetailed`, `verifyTreeSignatureDetailed` — server-side auth helpers |
23
+ | **Lease/Watermark** | `LeaseStore`, `WatermarkStore`, `LeaseMonitor` — WOTS key-use accounting to prevent catastrophic key reuse |
24
+ | **TransactionService** | High-level prepare → sign → finalize lifecycle with receipt tracking |
25
+ | **MINIMA_CONSTANTS** | Canonical chain parameters (`WOTS_W=8`, `MAX_SIGNATURES=262144`, `ADDRESS_PREFIX="Mx"`) |
26
+
27
+ ## Usage
28
+
29
+ ### WOTS keypair and signing
30
+
31
+ ```typescript
32
+ import { wotsKeypairFromSeed, wotsSign, wotsVerify } from '@totemsdk/core';
33
+
34
+ const seed = crypto.getRandomValues(new Uint8Array(32));
35
+ const keypair = wotsKeypairFromSeed(seed, 0);
36
+
37
+ // Sign — each (seed, index) pair is one-time use
38
+ const signature = wotsSign(seed, 0, message);
39
+
40
+ // Verify
41
+ const ok = wotsVerify(signature, message, keypair.pk);
42
+ ```
43
+
44
+ ### Per-address TreeKey (matches Minima Wallet.java)
45
+
46
+ ```typescript
47
+ import { createPerAddressTreeKey } from '@totemsdk/core';
48
+
49
+ // One independent 3-level tree per address index (0–63)
50
+ const treeKey = createPerAddressTreeKey(baseSeed, 0);
51
+
52
+ // Convert watermark (l1, l2) to a flat counter
53
+ const uses = l1 * 64 + l2;
54
+ treeKey.setUses(uses);
55
+ const { signature, publicKey, proofs } = treeKey.sign(data);
56
+ ```
57
+
58
+ ### Address derivation and verification
59
+
60
+ ```typescript
61
+ import { scriptToAddress, publicKeyToScript, verifySignature } from '@totemsdk/core';
62
+
63
+ const script = publicKeyToScript(publicKeyHex);
64
+ const address = scriptToAddress(script); // "Mx..."
65
+
66
+ const ok = verifySignature(address, message, signatureHex, publicKeyHex);
67
+ ```
68
+
69
+ ### Replay-safe challenge/response
70
+
71
+ ```typescript
72
+ import { createChallenge, validateChallenge } from '@totemsdk/core';
73
+
74
+ // Server: create a challenge
75
+ const challenge = createChallenge('my-dapp.example.com');
76
+
77
+ // Server: validate before accepting a signature
78
+ const { valid, error } = validateChallenge(challenge, {
79
+ maxAgeMs: 5 * 60 * 1000,
80
+ expectedDomain: 'my-dapp.example.com',
81
+ });
82
+ ```
83
+
84
+ ## See also
85
+
86
+ - [`@totemsdk/connect`](../connect) — dApp gateway built on top of core
87
+ - [`@totemsdk/node`](../node) — core wired for Node.js server-side use
88
+ - [`@totemsdk/wots-lease`](../wots-lease) — cloud-coordinated WOTS key safety
89
+ - [`@totemsdk/tx-builder`](../tx-builder) — full transaction construction
90
+ - [`@totemsdk/root-identity`](../root-identity) — multi-address identity from one seed
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ export { LeaseStore, WatermarkStore, LeaseMonitor, prepareLease, finalizeLease,
8
8
  export { TransactionService, TransactionLifecycle, TransactionLifecycleError, WatermarkExhaustedError, TransactionReceiptStore, type TransactionServiceConfig, type WotsSigningDependencies, type TransactionLifecycleConfig, type WatermarkSyncFunction, type PrepareResult, type TransactionReceiptStoreConfig, type PrepareRequest, type PrepareResponse, type SignRequest, type SignResult, type HierarchicalWitnessBundle, type WitnessBundle, type FinalizeRequest, type FinalizeResponse, type TransactionMetadata, type TransactionReceipt, type TransactionError, } from './tx/index.js';
9
9
  export type { WotsIndices } from './tx/index.js';
10
10
  export * from './utils.js';
11
- export { F, hex, fromHex, u16be, u32be, assert32, h, prfChainSeed, toWinternitzDigits, baseWWithChecksum, derivePKdigest, wotsKeypairFromSeed, wotsSign, wotsSignLegacy, wotsPkFromSig, wotsVerify, wotsPublicKeyFromSeed, type WotsKeypair, type WotsSignature, } from './wots.js';
11
+ export { F, hex, fromHex, u16be, u32be, assert32, h, prfChainSeed, toWinternitzDigits, baseWWithChecksum, derivePKdigest, wotsKeypairFromSeed, wotsSign, wotsSignLegacy, wotsPkFromSig, wotsVerify, wotsVerifyDigest, wotsPublicKeyFromSeed, type WotsKeypair, type WotsSignature, } from './wots.js';
12
12
  export * from './params.js';
13
13
  export { serializeMiniNumber, serializeMiniData, hashAllObjects, deriveChainSeedJava, deriveChildTreeSeedJava, hashObject, serializeMiniNumberZERO, serializeMiniNumberONE, writeHashToStream, javaHashAllObjects, indexToMiniDataBytes, derivePerAddressSeed, createMMREntryNumber, serializeMMREntryNumber, serializeMMRData, serializeMMREntry, precomputeTransactionCoinID, type MMREntryNumber as JavaMMREntryNumber, type MMRData as JavaMMRData, type MMREntry as JavaMMREntry, } from './javaStreamables.js';
14
14
  export { TreeKey, type KeyGenProgress, type ProgressCallback, TreeKeyNode, verifyTreeSignature, serializeTreeSignature, deserializeTreeSignature, getRootPublicKey, DEFAULT_KEYS_PER_LEVEL, DEFAULT_LEVELS, type SignatureProof, type TreeSignature, createPerAddressTreeKey, createPerAddressTreeKeyAsync, deriveAddressPublicKey, getPerAddressPublicKey, } from './treekey.js';
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ export { TransactionService, TransactionLifecycle, TransactionLifecycleError, Wa
13
13
  // Utilities first (canonical concatBytes source)
14
14
  export * from './utils.js';
15
15
  // WOTS Signatures - wots.concatBytes is intentionally shadowed by utils.concatBytes above
16
- export { F, hex, fromHex, u16be, u32be, assert32, h, prfChainSeed, toWinternitzDigits, baseWWithChecksum, derivePKdigest, wotsKeypairFromSeed, wotsSign, wotsSignLegacy, wotsPkFromSig, wotsVerify, wotsPublicKeyFromSeed, } from './wots.js';
16
+ export { F, hex, fromHex, u16be, u32be, assert32, h, prfChainSeed, toWinternitzDigits, baseWWithChecksum, derivePKdigest, wotsKeypairFromSeed, wotsSign, wotsSignLegacy, wotsPkFromSig, wotsVerify, wotsVerifyDigest, wotsPublicKeyFromSeed, } from './wots.js';
17
17
  // WOTS Params (from params.ts, imported by wots.ts)
18
18
  export * from './params.js';
19
19
  // Java-Compatible Serialization Helpers (for Minima-compatible seed derivation)
@@ -38,6 +38,8 @@ export class TransactionService {
38
38
  burn: params.burn || null,
39
39
  paramSet: this.config.paramSet || 'v2-spec',
40
40
  addressIndex: params.addressIndex,
41
+ walletMode: params.walletMode || 'AnonTree',
42
+ perAddressPublicKey: params.perAddressPublicKey ?? null,
41
43
  }, {
42
44
  headers: {
43
45
  'Content-Type': 'application/json',
@@ -14,6 +14,8 @@ export interface PrepareRequest {
14
14
  burn?: string;
15
15
  txId?: string;
16
16
  addressIndex?: number;
17
+ walletMode?: 'AnonTree' | 'RootTree';
18
+ perAddressPublicKey?: string;
17
19
  }
18
20
  export interface PrepareResponse {
19
21
  addressIndex: number;
@@ -28,6 +30,7 @@ export interface PrepareResponse {
28
30
  paramSet: string;
29
31
  leaseId: string;
30
32
  leaseTTL: number;
33
+ perAddressScript?: string | null;
31
34
  }
32
35
  export interface SignRequest {
33
36
  addressIndex: number;
package/dist/verify.js CHANGED
@@ -118,16 +118,17 @@ function timingSafeEqual(a, b) {
118
118
  function cryptoRandomBytes(length) {
119
119
  const buf = new Uint8Array(length);
120
120
  if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.getRandomValues) {
121
+ // Web Crypto API — available in browsers, Bare/Pear, and Node.js ≥ 19
121
122
  globalThis.crypto.getRandomValues(buf);
122
123
  }
124
+ else if (typeof process !== 'undefined' && process.versions?.node) {
125
+ // Node.js < 19 fallback — dynamic import avoids static resolution in Bare/bundlers
126
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
127
+ const nodeCrypto = Function('return require')()('crypto');
128
+ nodeCrypto.randomFillSync(buf);
129
+ }
123
130
  else {
124
- try {
125
- const { randomFillSync } = require('crypto');
126
- randomFillSync(buf);
127
- }
128
- catch {
129
- throw new Error('No cryptographically secure RNG available');
130
- }
131
+ throw new Error('No cryptographically secure RNG available in this runtime');
131
132
  }
132
133
  return buf;
133
134
  }
package/dist/wots.d.ts CHANGED
@@ -33,7 +33,7 @@ export declare function disableWotsLogger(): void;
33
33
  export declare function isWotsDebugEnabled(): boolean;
34
34
  export declare const F: (x: Uint8Array) => Uint8Array<ArrayBufferLike>;
35
35
  export declare const hex: (u: Uint8Array) => string;
36
- export declare const fromHex: (h: string) => Uint8Array<ArrayBuffer>;
36
+ export declare const fromHex: (h: string) => Uint8Array;
37
37
  export declare const concatBytes: (...arrs: Uint8Array[]) => Uint8Array<ArrayBuffer>;
38
38
  export declare const u16be: (n: number) => Uint8Array<ArrayBuffer>;
39
39
  export declare const u32be: (n: number) => Uint8Array<ArrayBuffer>;
package/dist/wots.js CHANGED
@@ -46,8 +46,19 @@ export function isWotsDebugEnabled() {
46
46
  }
47
47
  // === Canonical helpers ===
48
48
  export const F = (x) => sha3_256(x);
49
- export const hex = (u) => Buffer.from(u).toString('hex');
50
- export const fromHex = (h) => Uint8Array.from(Buffer.from(h.replace(/^0x/, ''), 'hex'));
49
+ export const hex = (u) => {
50
+ let s = '';
51
+ for (let i = 0; i < u.length; i++)
52
+ s += u[i].toString(16).padStart(2, '0');
53
+ return s;
54
+ };
55
+ export const fromHex = (h) => {
56
+ const s = h.replace(/^0x/, '');
57
+ const out = new Uint8Array(s.length >> 1);
58
+ for (let i = 0; i < out.length; i++)
59
+ out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16);
60
+ return out;
61
+ };
51
62
  export const concatBytes = (...arrs) => {
52
63
  const len = arrs.reduce((n, a) => n + a.length, 0);
53
64
  const out = new Uint8Array(len);
@@ -490,7 +501,12 @@ export function wotsVerify(sig, message, pkFull, ps = getParamSet()) {
490
501
  }
491
502
  // Compare FULL 1088-byte reconstructed key to expected FULL public key
492
503
  // This matches Java: resp.isEqual(zPublicKey) where both are 1088 bytes
493
- return Buffer.from(buf).equals(Buffer.from(pkFull));
504
+ if (buf.length !== pkFull.length)
505
+ return false;
506
+ let diff = 0;
507
+ for (let i = 0; i < buf.length; i++)
508
+ diff |= buf[i] ^ pkFull[i];
509
+ return diff === 0;
494
510
  }
495
511
  /**
496
512
  * Legacy verify function that accepts a 32-byte digest
@@ -512,7 +528,12 @@ export function wotsVerifyDigest(sig, message, pkDigest, ps = getParamSet()) {
512
528
  buf.set(top, j * 32);
513
529
  }
514
530
  const recomputed = F(buf);
515
- return Buffer.from(recomputed).equals(Buffer.from(pkDigest));
531
+ if (recomputed.length !== pkDigest.length)
532
+ return false;
533
+ let diff = 0;
534
+ for (let i = 0; i < recomputed.length; i++)
535
+ diff |= recomputed[i] ^ pkDigest[i];
536
+ return diff === 0;
516
537
  }
517
538
  /**
518
539
  * Generate WOTS public key from seed (convenience wrapper)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@totemsdk/core",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Core cryptographic primitives for Totem SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,12 +28,6 @@
28
28
  },
29
29
  "./test-vectors.json": "./test-vectors.json"
30
30
  },
31
- "scripts": {
32
- "build": "tsc",
33
- "clean": "rm -rf dist",
34
- "test": "jest --passWithNoTests",
35
- "gen:pkdigest": "node scripts/gen_wots_pkdigest_vectors.cjs"
36
- },
37
31
  "files": [
38
32
  "dist",
39
33
  "src",
@@ -62,5 +56,11 @@
62
56
  "publishConfig": {
63
57
  "access": "public"
64
58
  },
65
- "license": "MIT"
66
- }
59
+ "license": "MIT",
60
+ "scripts": {
61
+ "build": "tsc",
62
+ "clean": "rm -rf dist",
63
+ "test": "jest --passWithNoTests",
64
+ "gen:pkdigest": "node scripts/gen_wots_pkdigest_vectors.cjs"
65
+ }
66
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * RootTree prepare() propagation tests
3
+ *
4
+ * Verifies that TransactionService.prepare() correctly forwards walletMode
5
+ * and perAddressPublicKey in the POST body so the /prepare endpoint can
6
+ * apply RootTree-specific validation and return perAddressScript.
7
+ */
8
+
9
+ import { TransactionService } from '../tx/TransactionService.js';
10
+ import type { TransactionServiceConfig } from '../tx/TransactionService.js';
11
+ import type { HttpClient } from '../adapters/index.js';
12
+
13
+ function makeMockHttp(capturedBodies: any[]): HttpClient {
14
+ return {
15
+ post: async <T>(_url: string, body: any): Promise<{ ok: boolean; status: number; data: T }> => {
16
+ capturedBodies.push({ body });
17
+ return {
18
+ ok: true,
19
+ status: 200,
20
+ data: {
21
+ addressIndex: 0,
22
+ l1: 0,
23
+ l2: 0,
24
+ leaseToken: 'mock-lease-token',
25
+ digestTx: '0x' + 'ab'.repeat(32),
26
+ digestL2: null,
27
+ digestL3: null,
28
+ txId: 'tx-mock',
29
+ rootPublicKey: '0x' + 'ff'.repeat(32),
30
+ paramSet: 'v2-spec',
31
+ leaseId: 'lease-mock',
32
+ leaseTTL: 120000,
33
+ perAddressScript: 'RETURN SIGNEDBY(0x' + 'ff'.repeat(32) + ')',
34
+ } as T,
35
+ };
36
+ },
37
+ get: async <T>(): Promise<{ ok: boolean; status: number; data: T }> => ({
38
+ ok: true, status: 200, data: {} as T,
39
+ }),
40
+ };
41
+ }
42
+
43
+ const config: TransactionServiceConfig = {
44
+ baseUrl: 'https://api.example.com',
45
+ apiKey: 'test-api-key',
46
+ paramSet: 'v2-spec',
47
+ };
48
+
49
+ const MOCK_ROOT_PK = '0x' + 'aa'.repeat(32);
50
+ const MOCK_PER_ADDR_PK = '0x' + 'bb'.repeat(32);
51
+
52
+ describe('TransactionService.prepare() — RootTree propagation', () => {
53
+ it('sends walletMode=AnonTree by default', async () => {
54
+ const bodies: any[] = [];
55
+ const svc = new TransactionService(makeMockHttp(bodies), config);
56
+
57
+ await svc.prepare(
58
+ { to: 'MxABC', amount: '1', addressIndex: 0 },
59
+ MOCK_ROOT_PK
60
+ );
61
+
62
+ expect(bodies).toHaveLength(1);
63
+ expect(bodies[0].body.walletMode).toBe('AnonTree');
64
+ expect(bodies[0].body.perAddressPublicKey).toBeNull();
65
+ });
66
+
67
+ it('forwards walletMode=RootTree and perAddressPublicKey in POST body', async () => {
68
+ const bodies: any[] = [];
69
+ const svc = new TransactionService(makeMockHttp(bodies), config);
70
+
71
+ await svc.prepare(
72
+ {
73
+ to: 'MxABC',
74
+ amount: '1',
75
+ addressIndex: 0,
76
+ walletMode: 'RootTree',
77
+ perAddressPublicKey: MOCK_PER_ADDR_PK,
78
+ },
79
+ MOCK_ROOT_PK
80
+ );
81
+
82
+ expect(bodies).toHaveLength(1);
83
+ const sent = bodies[0].body;
84
+ expect(sent.walletMode).toBe('RootTree');
85
+ expect(sent.perAddressPublicKey).toBe(MOCK_PER_ADDR_PK);
86
+ expect(sent.rootPublicKey).toBe(MOCK_ROOT_PK);
87
+ });
88
+
89
+ it('PrepareResponse includes perAddressScript field from server', async () => {
90
+ const bodies: any[] = [];
91
+ const svc = new TransactionService(makeMockHttp(bodies), config);
92
+
93
+ const result = await svc.prepare(
94
+ { to: 'MxABC', amount: '1', addressIndex: 0, walletMode: 'RootTree', perAddressPublicKey: MOCK_PER_ADDR_PK },
95
+ MOCK_ROOT_PK
96
+ );
97
+
98
+ expect(result.perAddressScript).toBeDefined();
99
+ expect(typeof result.perAddressScript).toBe('string');
100
+ });
101
+ });
package/src/index.ts CHANGED
@@ -81,6 +81,7 @@ export {
81
81
  wotsSignLegacy,
82
82
  wotsPkFromSig,
83
83
  wotsVerify,
84
+ wotsVerifyDigest,
84
85
  wotsPublicKeyFromSeed,
85
86
  type WotsKeypair,
86
87
  type WotsSignature,
@@ -83,6 +83,8 @@ export class TransactionService {
83
83
  burn: params.burn || null,
84
84
  paramSet: this.config.paramSet || 'v2-spec',
85
85
  addressIndex: params.addressIndex,
86
+ walletMode: params.walletMode || 'AnonTree',
87
+ perAddressPublicKey: params.perAddressPublicKey ?? null,
86
88
  }, {
87
89
  headers: {
88
90
  'Content-Type': 'application/json',
package/src/tx/types.ts CHANGED
@@ -15,7 +15,9 @@ export interface PrepareRequest {
15
15
  tokenId?: string;
16
16
  burn?: string;
17
17
  txId?: string;
18
- addressIndex?: number; // Required by v2-spec backend (0-63, must match the coin's address)
18
+ addressIndex?: number; // Required by v2-spec backend (0-63, must match the coin's address)
19
+ walletMode?: 'AnonTree' | 'RootTree'; // Key derivation scheme — omit for legacy/AnonTree
20
+ perAddressPublicKey?: string; // Hex pubkey of per-address TreeKey root (required for RootTree validation)
19
21
  }
20
22
 
21
23
  export interface PrepareResponse {
@@ -31,6 +33,7 @@ export interface PrepareResponse {
31
33
  paramSet: string;
32
34
  leaseId: string;
33
35
  leaseTTL: number;
36
+ perAddressScript?: string | null; // RETURN SIGNEDBY(<perAddressPublicKey>) — present in RootTree mode
34
37
  }
35
38
 
36
39
  export interface SignRequest {
package/src/verify.ts CHANGED
@@ -157,14 +157,15 @@ function timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean {
157
157
  function cryptoRandomBytes(length: number): Uint8Array {
158
158
  const buf = new Uint8Array(length);
159
159
  if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.getRandomValues) {
160
+ // Web Crypto API — available in browsers, Bare/Pear, and Node.js ≥ 19
160
161
  globalThis.crypto.getRandomValues(buf);
162
+ } else if (typeof process !== 'undefined' && process.versions?.node) {
163
+ // Node.js < 19 fallback — dynamic import avoids static resolution in Bare/bundlers
164
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
165
+ const nodeCrypto = Function('return require')()('crypto') as { randomFillSync: (buf: Uint8Array) => void };
166
+ nodeCrypto.randomFillSync(buf);
161
167
  } else {
162
- try {
163
- const { randomFillSync } = require('crypto');
164
- randomFillSync(buf);
165
- } catch {
166
- throw new Error('No cryptographically secure RNG available');
167
- }
168
+ throw new Error('No cryptographically secure RNG available in this runtime');
168
169
  }
169
170
  return buf;
170
171
  }
package/src/wots.js CHANGED
@@ -70,9 +70,9 @@ function isWotsDebugEnabled() {
70
70
  // === Canonical helpers ===
71
71
  const F = (x) => (0, sha3_1.sha3_256)(x);
72
72
  exports.F = F;
73
- const hex = (u) => Buffer.from(u).toString('hex');
73
+ const hex = (u) => { let s = ''; for (let i = 0; i < u.length; i++) s += u[i].toString(16).padStart(2, '0'); return s; };
74
74
  exports.hex = hex;
75
- const fromHex = (h) => Uint8Array.from(Buffer.from(h.replace(/^0x/, ''), 'hex'));
75
+ const fromHex = (h) => { const s = h.replace(/^0x/, ''); const out = new Uint8Array(s.length >> 1); for (let i = 0; i < out.length; i++) out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16); return out; };
76
76
  exports.fromHex = fromHex;
77
77
  const concatBytes = (...arrs) => {
78
78
  const len = arrs.reduce((n, a) => n + a.length, 0);
@@ -511,7 +511,10 @@ function wotsVerify(sig, message, pkFull, ps = (0, params_1.getParamSet)()) {
511
511
  }
512
512
  // Compare FULL 1088-byte reconstructed key to expected FULL public key
513
513
  // This matches Java: resp.isEqual(zPublicKey) where both are 1088 bytes
514
- return Buffer.from(buf).equals(Buffer.from(pkFull));
514
+ if (buf.length !== pkFull.length) return false;
515
+ let diff = 0;
516
+ for (let i = 0; i < buf.length; i++) diff |= buf[i] ^ pkFull[i];
517
+ return diff === 0;
515
518
  }
516
519
  /**
517
520
  * Legacy verify function that accepts a 32-byte digest
@@ -533,7 +536,10 @@ function wotsVerifyDigest(sig, message, pkDigest, ps = (0, params_1.getParamSet)
533
536
  buf.set(top, j * 32);
534
537
  }
535
538
  const recomputed = (0, exports.F)(buf);
536
- return Buffer.from(recomputed).equals(Buffer.from(pkDigest));
539
+ if (recomputed.length !== pkDigest.length) return false;
540
+ let diff = 0;
541
+ for (let i = 0; i < recomputed.length; i++) diff |= recomputed[i] ^ pkDigest[i];
542
+ return diff === 0;
537
543
  }
538
544
  /**
539
545
  * Generate WOTS public key from seed (convenience wrapper)
package/src/wots.ts CHANGED
@@ -53,8 +53,17 @@ export function isWotsDebugEnabled(): boolean {
53
53
 
54
54
  // === Canonical helpers ===
55
55
  export const F = (x: Uint8Array) => sha3_256(x);
56
- export const hex = (u: Uint8Array) => Buffer.from(u).toString('hex');
57
- export const fromHex = (h: string) => Uint8Array.from(Buffer.from(h.replace(/^0x/, ''), 'hex'));
56
+ export const hex = (u: Uint8Array): string => {
57
+ let s = '';
58
+ for (let i = 0; i < u.length; i++) s += u[i].toString(16).padStart(2, '0');
59
+ return s;
60
+ };
61
+ export const fromHex = (h: string): Uint8Array => {
62
+ const s = h.replace(/^0x/, '');
63
+ const out = new Uint8Array(s.length >> 1);
64
+ for (let i = 0; i < out.length; i++) out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16);
65
+ return out;
66
+ };
58
67
  export const concatBytes = (...arrs: Uint8Array[]) => {
59
68
  const len = arrs.reduce((n, a) => n + a.length, 0);
60
69
  const out = new Uint8Array(len);
@@ -579,7 +588,10 @@ export function wotsVerify(sig: Uint8Array, message: Uint8Array, pkFull: Uint8Ar
579
588
 
580
589
  // Compare FULL 1088-byte reconstructed key to expected FULL public key
581
590
  // This matches Java: resp.isEqual(zPublicKey) where both are 1088 bytes
582
- return Buffer.from(buf).equals(Buffer.from(pkFull));
591
+ if (buf.length !== pkFull.length) return false;
592
+ let diff = 0;
593
+ for (let i = 0; i < buf.length; i++) diff |= buf[i] ^ pkFull[i];
594
+ return diff === 0;
583
595
  }
584
596
 
585
597
  /**
@@ -606,7 +618,10 @@ export function wotsVerifyDigest(sig: Uint8Array, message: Uint8Array, pkDigest:
606
618
  }
607
619
 
608
620
  const recomputed = F(buf);
609
- return Buffer.from(recomputed).equals(Buffer.from(pkDigest));
621
+ if (recomputed.length !== pkDigest.length) return false;
622
+ let diff = 0;
623
+ for (let i = 0; i < recomputed.length; i++) diff |= recomputed[i] ^ pkDigest[i];
624
+ return diff === 0;
610
625
  }
611
626
 
612
627