@bcts/hubert 1.0.0-alpha.17

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.
Files changed (104) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +18 -0
  3. package/dist/arid-derivation-1CJuU-kZ.cjs +150 -0
  4. package/dist/arid-derivation-1CJuU-kZ.cjs.map +1 -0
  5. package/dist/arid-derivation-CbqACjdg.mjs +126 -0
  6. package/dist/arid-derivation-CbqACjdg.mjs.map +1 -0
  7. package/dist/bin/hubert.cjs +384 -0
  8. package/dist/bin/hubert.cjs.map +1 -0
  9. package/dist/bin/hubert.d.cts +1 -0
  10. package/dist/bin/hubert.d.mts +1 -0
  11. package/dist/bin/hubert.mjs +383 -0
  12. package/dist/bin/hubert.mjs.map +1 -0
  13. package/dist/chunk-CbDLau6x.cjs +34 -0
  14. package/dist/hybrid/index.cjs +14 -0
  15. package/dist/hybrid/index.d.cts +3 -0
  16. package/dist/hybrid/index.d.mts +3 -0
  17. package/dist/hybrid/index.mjs +6 -0
  18. package/dist/hybrid-BZhumygj.mjs +356 -0
  19. package/dist/hybrid-BZhumygj.mjs.map +1 -0
  20. package/dist/hybrid-dX5JLumO.cjs +410 -0
  21. package/dist/hybrid-dX5JLumO.cjs.map +1 -0
  22. package/dist/index-BEzpUC7r.d.mts +380 -0
  23. package/dist/index-BEzpUC7r.d.mts.map +1 -0
  24. package/dist/index-C2F6ugLL.d.mts +210 -0
  25. package/dist/index-C2F6ugLL.d.mts.map +1 -0
  26. package/dist/index-CUnDouMb.d.mts +215 -0
  27. package/dist/index-CUnDouMb.d.mts.map +1 -0
  28. package/dist/index-CV6lZJqY.d.cts +380 -0
  29. package/dist/index-CV6lZJqY.d.cts.map +1 -0
  30. package/dist/index-CY3TCzIm.d.cts +217 -0
  31. package/dist/index-CY3TCzIm.d.cts.map +1 -0
  32. package/dist/index-DEr4SR1J.d.cts +215 -0
  33. package/dist/index-DEr4SR1J.d.cts.map +1 -0
  34. package/dist/index-T1LHanIb.d.mts +217 -0
  35. package/dist/index-T1LHanIb.d.mts.map +1 -0
  36. package/dist/index-jyzuOhFB.d.cts +210 -0
  37. package/dist/index-jyzuOhFB.d.cts.map +1 -0
  38. package/dist/index.cjs +60 -0
  39. package/dist/index.d.cts +161 -0
  40. package/dist/index.d.cts.map +1 -0
  41. package/dist/index.d.mts +161 -0
  42. package/dist/index.d.mts.map +1 -0
  43. package/dist/index.mjs +10 -0
  44. package/dist/ipfs/index.cjs +13 -0
  45. package/dist/ipfs/index.d.cts +3 -0
  46. package/dist/ipfs/index.d.mts +3 -0
  47. package/dist/ipfs/index.mjs +5 -0
  48. package/dist/ipfs-BRMMCBjv.mjs +1 -0
  49. package/dist/ipfs-CetOVQcO.cjs +0 -0
  50. package/dist/kv-BAmhmMOo.cjs +425 -0
  51. package/dist/kv-BAmhmMOo.cjs.map +1 -0
  52. package/dist/kv-C-emxv0w.mjs +375 -0
  53. package/dist/kv-C-emxv0w.mjs.map +1 -0
  54. package/dist/kv-DJiKvypY.mjs +403 -0
  55. package/dist/kv-DJiKvypY.mjs.map +1 -0
  56. package/dist/kv-store-DmngWWuw.d.mts +183 -0
  57. package/dist/kv-store-DmngWWuw.d.mts.map +1 -0
  58. package/dist/kv-store-ww-AUyLd.d.cts +183 -0
  59. package/dist/kv-store-ww-AUyLd.d.cts.map +1 -0
  60. package/dist/kv-yjvQa_LH.cjs +457 -0
  61. package/dist/kv-yjvQa_LH.cjs.map +1 -0
  62. package/dist/logging-hmzNzifq.mjs +158 -0
  63. package/dist/logging-hmzNzifq.mjs.map +1 -0
  64. package/dist/logging-qc9uMgil.cjs +212 -0
  65. package/dist/logging-qc9uMgil.cjs.map +1 -0
  66. package/dist/mainline/index.cjs +12 -0
  67. package/dist/mainline/index.d.cts +3 -0
  68. package/dist/mainline/index.d.mts +3 -0
  69. package/dist/mainline/index.mjs +5 -0
  70. package/dist/mainline-D_jfeFMh.cjs +0 -0
  71. package/dist/mainline-cFIuXbo-.mjs +1 -0
  72. package/dist/server/index.cjs +14 -0
  73. package/dist/server/index.d.cts +3 -0
  74. package/dist/server/index.d.mts +3 -0
  75. package/dist/server/index.mjs +3 -0
  76. package/dist/server-BBNRZ30D.cjs +912 -0
  77. package/dist/server-BBNRZ30D.cjs.map +1 -0
  78. package/dist/server-DVyk9gqU.mjs +836 -0
  79. package/dist/server-DVyk9gqU.mjs.map +1 -0
  80. package/package.json +125 -0
  81. package/src/arid-derivation.ts +155 -0
  82. package/src/bin/hubert.ts +667 -0
  83. package/src/error.ts +89 -0
  84. package/src/hybrid/error.ts +77 -0
  85. package/src/hybrid/index.ts +24 -0
  86. package/src/hybrid/kv.ts +236 -0
  87. package/src/hybrid/reference.ts +176 -0
  88. package/src/index.ts +145 -0
  89. package/src/ipfs/error.ts +83 -0
  90. package/src/ipfs/index.ts +24 -0
  91. package/src/ipfs/kv.ts +476 -0
  92. package/src/ipfs/value.ts +85 -0
  93. package/src/kv-store.ts +128 -0
  94. package/src/logging.ts +88 -0
  95. package/src/mainline/error.ts +108 -0
  96. package/src/mainline/index.ts +23 -0
  97. package/src/mainline/kv.ts +411 -0
  98. package/src/server/error.ts +83 -0
  99. package/src/server/index.ts +29 -0
  100. package/src/server/kv.ts +211 -0
  101. package/src/server/memory-kv.ts +191 -0
  102. package/src/server/server-kv.ts +92 -0
  103. package/src/server/server.ts +369 -0
  104. package/src/server/sqlite-kv.ts +295 -0
package/LICENSE ADDED
@@ -0,0 +1,48 @@
1
+ Copyright © 2025 Blockchain Commons, LLC
2
+ Copyright © 2025-2026 Leonardo Amoroso Custodio
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ Subject to the terms and conditions of this license, each copyright holder and
15
+ contributor hereby grants to those receiving rights under this license a
16
+ perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
17
+ (except for failure to satisfy the conditions of this license) patent license to
18
+ make, have made, use, offer to sell, sell, import, and otherwise transfer this
19
+ software, where such license applies only to those patent claims, already
20
+ acquired or hereafter acquired, licensable by such copyright holder or
21
+ contributor that are necessarily infringed by:
22
+
23
+ (a) their Contribution(s) (the licensed copyrights of copyright holders and
24
+ non-copyrightable additions of contributors, in source or binary form)
25
+ alone; or
26
+
27
+ (b) combination of their Contribution(s) with the work of authorship to
28
+ which such Contribution(s) was added by such copyright holder or
29
+ contributor, if, at the time the Contribution is added, such addition causes
30
+ such combination to be necessarily infringed. The patent license shall not
31
+ apply to any other combinations which include the Contribution.
32
+
33
+ Except as expressly stated above, no rights or licenses from any copyright
34
+ holder or contributor is granted under this license, whether expressly, by
35
+ implication, estoppel or otherwise.
36
+
37
+ DISCLAIMER
38
+
39
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
40
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
41
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
42
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
43
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
44
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
45
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
46
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
47
+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
48
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # Hubert - Distributed Infrastructure for Secure Multiparty Transactions
2
+
3
+ > Disclaimer: This package is under active development and APIs may change.
4
+
5
+ ## Introduction
6
+
7
+ Hubert provides distributed infrastructure for secure multiparty transactions, designed to support FROST threshold signature protocols. It enables distributed key-value storage using ARID (Apparently Random Identifier) addressing with multiple storage backends.
8
+
9
+ **Key Features:**
10
+ - **ARID-based Addressing**: Cryptographic identifiers that appear random but are deterministically derived
11
+ - **Multiple Storage Backends**: Mainline DHT, IPFS, Hybrid (DHT+IPFS), and Server modes
12
+ - **Write-once Semantics**: Content-addressed storage that cannot be modified once written
13
+ - **Data Obfuscation**: ChaCha20 encryption using ARID-derived keys
14
+ - **Gordian Envelope**: All data is stored as Gordian Envelope structures
15
+
16
+ ## Rust Reference Implementation
17
+
18
+ This TypeScript implementation is based on [hubert-rust](https://github.com/BlockchainCommons/hubert-rust) **v0.5.0** ([commit](https://github.com/BlockchainCommons/hubert-rust/tree/master)).
@@ -0,0 +1,150 @@
1
+ const require_chunk = require('./chunk-CbDLau6x.cjs');
2
+ let _bcts_crypto = require("@bcts/crypto");
3
+ let _noble_ciphers_chacha_js = require("@noble/ciphers/chacha.js");
4
+
5
+ //#region src/arid-derivation.ts
6
+ /**
7
+ * Derive a deterministic key from an ARID using a specific salt.
8
+ *
9
+ * Uses HKDF to derive key material from the ARID, ensuring that:
10
+ * - Same ARID always produces same key for a given salt
11
+ * - Keys are cryptographically derived (not guessable)
12
+ * - Collision resistance inherited from ARID
13
+ * - No identifying information in the key (fully anonymized)
14
+ *
15
+ * Port of `derive_key()` from arid_derivation.rs lines 8-29.
16
+ *
17
+ * @param salt - Domain-specific salt to ensure different backends derive different keys
18
+ * @param arid - The ARID to derive from
19
+ * @param outputLen - Length of output in bytes (typically 20 or 32)
20
+ * @returns Derived key bytes
21
+ * @category ARID Derivation
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const key = deriveKey(new TextEncoder().encode("my-salt"), arid, 32);
26
+ * ```
27
+ */
28
+ function deriveKey(salt, arid, outputLen) {
29
+ return (0, _bcts_crypto.hkdfHmacSha256)(arid.data(), salt, outputLen);
30
+ }
31
+ /**
32
+ * Salt for IPFS IPNS key derivation.
33
+ * @internal
34
+ */
35
+ const IPFS_KEY_NAME_SALT = new TextEncoder().encode("hubert-ipfs-ipns-v1");
36
+ /**
37
+ * Salt for Mainline DHT key derivation.
38
+ * @internal
39
+ */
40
+ const MAINLINE_KEY_SALT = new TextEncoder().encode("hubert-mainline-dht-v1");
41
+ /**
42
+ * Salt for obfuscation key derivation.
43
+ * @internal
44
+ */
45
+ const OBFUSCATION_SALT = new TextEncoder().encode("hubert-obfuscation-v1");
46
+ /**
47
+ * Derive an IPNS key name from an ARID.
48
+ *
49
+ * Returns a 64-character hex string suitable for use as an IPFS key name.
50
+ *
51
+ * Port of `derive_ipfs_key_name()` from arid_derivation.rs lines 31-37.
52
+ *
53
+ * @param arid - The ARID to derive from
54
+ * @returns 64-character hex string
55
+ * @category ARID Derivation
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * const keyName = deriveIpfsKeyName(arid);
60
+ * // => "a1b2c3d4e5f6..." (64 hex characters)
61
+ * ```
62
+ */
63
+ function deriveIpfsKeyName(arid) {
64
+ return bytesToHex(deriveKey(IPFS_KEY_NAME_SALT, arid, 32));
65
+ }
66
+ /**
67
+ * Derive Mainline DHT key material from an ARID.
68
+ *
69
+ * Returns 20 bytes of key material (SHA-1 compatible length).
70
+ *
71
+ * Port of `derive_mainline_key()` from arid_derivation.rs lines 39-45.
72
+ *
73
+ * @param arid - The ARID to derive from
74
+ * @returns 20 bytes of key material
75
+ * @category ARID Derivation
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * const key = deriveMainlineKey(arid);
80
+ * // => Uint8Array(20) [...]
81
+ * ```
82
+ */
83
+ function deriveMainlineKey(arid) {
84
+ return deriveKey(MAINLINE_KEY_SALT, arid, 20);
85
+ }
86
+ /**
87
+ * Obfuscate or deobfuscate data using ChaCha20 with an ARID-derived key.
88
+ *
89
+ * This function uses ChaCha20 as a stream cipher to XOR the data with a
90
+ * keystream derived from the ARID. Since XOR is symmetric, the same function
91
+ * is used for both obfuscation and deobfuscation.
92
+ *
93
+ * The result appears as uniform random data to anyone who doesn't have the
94
+ * ARID, hiding both the structure and content of the reference envelope.
95
+ *
96
+ * Port of `obfuscate_with_arid()` from arid_derivation.rs lines 47-91.
97
+ *
98
+ * @param arid - The ARID used to derive the obfuscation key
99
+ * @param data - The data to obfuscate or deobfuscate
100
+ * @returns The obfuscated (or deobfuscated) data
101
+ * @category ARID Derivation
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const obfuscated = obfuscateWithArid(arid, plaintext);
106
+ * const deobfuscated = obfuscateWithArid(arid, obfuscated);
107
+ * // deobfuscated === plaintext
108
+ * ```
109
+ */
110
+ function obfuscateWithArid(arid, data) {
111
+ if (data.length === 0) return new Uint8Array(0);
112
+ const key = deriveKey(OBFUSCATION_SALT, arid, 32);
113
+ const iv = new Uint8Array(12);
114
+ for (let i = 0; i < 12; i++) iv[i] = key[31 - i];
115
+ return (0, _noble_ciphers_chacha_js.chacha20)(key, iv, data);
116
+ }
117
+ /**
118
+ * Convert bytes to lowercase hex string.
119
+ * @internal
120
+ */
121
+ function bytesToHex(bytes) {
122
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
123
+ }
124
+
125
+ //#endregion
126
+ Object.defineProperty(exports, 'deriveIpfsKeyName', {
127
+ enumerable: true,
128
+ get: function () {
129
+ return deriveIpfsKeyName;
130
+ }
131
+ });
132
+ Object.defineProperty(exports, 'deriveKey', {
133
+ enumerable: true,
134
+ get: function () {
135
+ return deriveKey;
136
+ }
137
+ });
138
+ Object.defineProperty(exports, 'deriveMainlineKey', {
139
+ enumerable: true,
140
+ get: function () {
141
+ return deriveMainlineKey;
142
+ }
143
+ });
144
+ Object.defineProperty(exports, 'obfuscateWithArid', {
145
+ enumerable: true,
146
+ get: function () {
147
+ return obfuscateWithArid;
148
+ }
149
+ });
150
+ //# sourceMappingURL=arid-derivation-1CJuU-kZ.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arid-derivation-1CJuU-kZ.cjs","names":[],"sources":["../src/arid-derivation.ts"],"sourcesContent":["/**\n * ARID key derivation and obfuscation utilities.\n *\n * Port of arid_derivation.rs from hubert-rust.\n *\n * @module\n */\n\nimport { type ARID } from \"@bcts/components\";\nimport { hkdfHmacSha256 } from \"@bcts/crypto\";\nimport { chacha20 } from \"@noble/ciphers/chacha.js\";\n\n/**\n * Derive a deterministic key from an ARID using a specific salt.\n *\n * Uses HKDF to derive key material from the ARID, ensuring that:\n * - Same ARID always produces same key for a given salt\n * - Keys are cryptographically derived (not guessable)\n * - Collision resistance inherited from ARID\n * - No identifying information in the key (fully anonymized)\n *\n * Port of `derive_key()` from arid_derivation.rs lines 8-29.\n *\n * @param salt - Domain-specific salt to ensure different backends derive different keys\n * @param arid - The ARID to derive from\n * @param outputLen - Length of output in bytes (typically 20 or 32)\n * @returns Derived key bytes\n * @category ARID Derivation\n *\n * @example\n * ```typescript\n * const key = deriveKey(new TextEncoder().encode(\"my-salt\"), arid, 32);\n * ```\n */\nexport function deriveKey(salt: Uint8Array, arid: ARID, outputLen: number): Uint8Array {\n const aridBytes = arid.data();\n // Note: @bcts/crypto hkdfHmacSha256 takes (keyMaterial, salt, keyLen)\n // Rust bc-crypto takes (salt, ikm, keyLen)\n return hkdfHmacSha256(aridBytes, salt, outputLen);\n}\n\n/**\n * Salt for IPFS IPNS key derivation.\n * @internal\n */\nconst IPFS_KEY_NAME_SALT = new TextEncoder().encode(\"hubert-ipfs-ipns-v1\");\n\n/**\n * Salt for Mainline DHT key derivation.\n * @internal\n */\nconst MAINLINE_KEY_SALT = new TextEncoder().encode(\"hubert-mainline-dht-v1\");\n\n/**\n * Salt for obfuscation key derivation.\n * @internal\n */\nconst OBFUSCATION_SALT = new TextEncoder().encode(\"hubert-obfuscation-v1\");\n\n/**\n * Derive an IPNS key name from an ARID.\n *\n * Returns a 64-character hex string suitable for use as an IPFS key name.\n *\n * Port of `derive_ipfs_key_name()` from arid_derivation.rs lines 31-37.\n *\n * @param arid - The ARID to derive from\n * @returns 64-character hex string\n * @category ARID Derivation\n *\n * @example\n * ```typescript\n * const keyName = deriveIpfsKeyName(arid);\n * // => \"a1b2c3d4e5f6...\" (64 hex characters)\n * ```\n */\nexport function deriveIpfsKeyName(arid: ARID): string {\n const keyBytes = deriveKey(IPFS_KEY_NAME_SALT, arid, 32);\n return bytesToHex(keyBytes);\n}\n\n/**\n * Derive Mainline DHT key material from an ARID.\n *\n * Returns 20 bytes of key material (SHA-1 compatible length).\n *\n * Port of `derive_mainline_key()` from arid_derivation.rs lines 39-45.\n *\n * @param arid - The ARID to derive from\n * @returns 20 bytes of key material\n * @category ARID Derivation\n *\n * @example\n * ```typescript\n * const key = deriveMainlineKey(arid);\n * // => Uint8Array(20) [...]\n * ```\n */\nexport function deriveMainlineKey(arid: ARID): Uint8Array {\n return deriveKey(MAINLINE_KEY_SALT, arid, 20);\n}\n\n/**\n * Obfuscate or deobfuscate data using ChaCha20 with an ARID-derived key.\n *\n * This function uses ChaCha20 as a stream cipher to XOR the data with a\n * keystream derived from the ARID. Since XOR is symmetric, the same function\n * is used for both obfuscation and deobfuscation.\n *\n * The result appears as uniform random data to anyone who doesn't have the\n * ARID, hiding both the structure and content of the reference envelope.\n *\n * Port of `obfuscate_with_arid()` from arid_derivation.rs lines 47-91.\n *\n * @param arid - The ARID used to derive the obfuscation key\n * @param data - The data to obfuscate or deobfuscate\n * @returns The obfuscated (or deobfuscated) data\n * @category ARID Derivation\n *\n * @example\n * ```typescript\n * const obfuscated = obfuscateWithArid(arid, plaintext);\n * const deobfuscated = obfuscateWithArid(arid, obfuscated);\n * // deobfuscated === plaintext\n * ```\n */\nexport function obfuscateWithArid(arid: ARID, data: Uint8Array): Uint8Array {\n if (data.length === 0) {\n return new Uint8Array(0);\n }\n\n // Derive a 32-byte key from the ARID using HKDF with domain-specific salt\n const key = deriveKey(OBFUSCATION_SALT, arid, 32);\n\n // Derive IV from the key (last 12 bytes, reversed)\n // Match Rust: key.iter().rev().take(12).copied().collect()\n const iv = new Uint8Array(12);\n for (let i = 0; i < 12; i++) {\n iv[i] = key[31 - i];\n }\n\n // Use ChaCha20 to XOR the data\n // @noble/ciphers chacha20 takes (key, nonce, data) and returns encrypted data\n return chacha20(key, iv, data);\n}\n\n/**\n * Convert bytes to lowercase hex string.\n * @internal\n */\nfunction bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,UAAU,MAAkB,MAAY,WAA+B;AAIrF,yCAHkB,KAAK,MAAM,EAGI,MAAM,UAAU;;;;;;AAOnD,MAAM,qBAAqB,IAAI,aAAa,CAAC,OAAO,sBAAsB;;;;;AAM1E,MAAM,oBAAoB,IAAI,aAAa,CAAC,OAAO,yBAAyB;;;;;AAM5E,MAAM,mBAAmB,IAAI,aAAa,CAAC,OAAO,wBAAwB;;;;;;;;;;;;;;;;;;AAmB1E,SAAgB,kBAAkB,MAAoB;AAEpD,QAAO,WADU,UAAU,oBAAoB,MAAM,GAAG,CAC7B;;;;;;;;;;;;;;;;;;;AAoB7B,SAAgB,kBAAkB,MAAwB;AACxD,QAAO,UAAU,mBAAmB,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;AA2B/C,SAAgB,kBAAkB,MAAY,MAA8B;AAC1E,KAAI,KAAK,WAAW,EAClB,QAAO,IAAI,WAAW,EAAE;CAI1B,MAAM,MAAM,UAAU,kBAAkB,MAAM,GAAG;CAIjD,MAAM,KAAK,IAAI,WAAW,GAAG;AAC7B,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,IACtB,IAAG,KAAK,IAAI,KAAK;AAKnB,+CAAgB,KAAK,IAAI,KAAK;;;;;;AAOhC,SAAS,WAAW,OAA2B;AAC7C,QAAO,MAAM,KAAK,MAAM,CACrB,KAAK,MAAM,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CAC3C,KAAK,GAAG"}
@@ -0,0 +1,126 @@
1
+ import { hkdfHmacSha256 } from "@bcts/crypto";
2
+ import { chacha20 } from "@noble/ciphers/chacha.js";
3
+
4
+ //#region src/arid-derivation.ts
5
+ /**
6
+ * Derive a deterministic key from an ARID using a specific salt.
7
+ *
8
+ * Uses HKDF to derive key material from the ARID, ensuring that:
9
+ * - Same ARID always produces same key for a given salt
10
+ * - Keys are cryptographically derived (not guessable)
11
+ * - Collision resistance inherited from ARID
12
+ * - No identifying information in the key (fully anonymized)
13
+ *
14
+ * Port of `derive_key()` from arid_derivation.rs lines 8-29.
15
+ *
16
+ * @param salt - Domain-specific salt to ensure different backends derive different keys
17
+ * @param arid - The ARID to derive from
18
+ * @param outputLen - Length of output in bytes (typically 20 or 32)
19
+ * @returns Derived key bytes
20
+ * @category ARID Derivation
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const key = deriveKey(new TextEncoder().encode("my-salt"), arid, 32);
25
+ * ```
26
+ */
27
+ function deriveKey(salt, arid, outputLen) {
28
+ return hkdfHmacSha256(arid.data(), salt, outputLen);
29
+ }
30
+ /**
31
+ * Salt for IPFS IPNS key derivation.
32
+ * @internal
33
+ */
34
+ const IPFS_KEY_NAME_SALT = new TextEncoder().encode("hubert-ipfs-ipns-v1");
35
+ /**
36
+ * Salt for Mainline DHT key derivation.
37
+ * @internal
38
+ */
39
+ const MAINLINE_KEY_SALT = new TextEncoder().encode("hubert-mainline-dht-v1");
40
+ /**
41
+ * Salt for obfuscation key derivation.
42
+ * @internal
43
+ */
44
+ const OBFUSCATION_SALT = new TextEncoder().encode("hubert-obfuscation-v1");
45
+ /**
46
+ * Derive an IPNS key name from an ARID.
47
+ *
48
+ * Returns a 64-character hex string suitable for use as an IPFS key name.
49
+ *
50
+ * Port of `derive_ipfs_key_name()` from arid_derivation.rs lines 31-37.
51
+ *
52
+ * @param arid - The ARID to derive from
53
+ * @returns 64-character hex string
54
+ * @category ARID Derivation
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const keyName = deriveIpfsKeyName(arid);
59
+ * // => "a1b2c3d4e5f6..." (64 hex characters)
60
+ * ```
61
+ */
62
+ function deriveIpfsKeyName(arid) {
63
+ return bytesToHex(deriveKey(IPFS_KEY_NAME_SALT, arid, 32));
64
+ }
65
+ /**
66
+ * Derive Mainline DHT key material from an ARID.
67
+ *
68
+ * Returns 20 bytes of key material (SHA-1 compatible length).
69
+ *
70
+ * Port of `derive_mainline_key()` from arid_derivation.rs lines 39-45.
71
+ *
72
+ * @param arid - The ARID to derive from
73
+ * @returns 20 bytes of key material
74
+ * @category ARID Derivation
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const key = deriveMainlineKey(arid);
79
+ * // => Uint8Array(20) [...]
80
+ * ```
81
+ */
82
+ function deriveMainlineKey(arid) {
83
+ return deriveKey(MAINLINE_KEY_SALT, arid, 20);
84
+ }
85
+ /**
86
+ * Obfuscate or deobfuscate data using ChaCha20 with an ARID-derived key.
87
+ *
88
+ * This function uses ChaCha20 as a stream cipher to XOR the data with a
89
+ * keystream derived from the ARID. Since XOR is symmetric, the same function
90
+ * is used for both obfuscation and deobfuscation.
91
+ *
92
+ * The result appears as uniform random data to anyone who doesn't have the
93
+ * ARID, hiding both the structure and content of the reference envelope.
94
+ *
95
+ * Port of `obfuscate_with_arid()` from arid_derivation.rs lines 47-91.
96
+ *
97
+ * @param arid - The ARID used to derive the obfuscation key
98
+ * @param data - The data to obfuscate or deobfuscate
99
+ * @returns The obfuscated (or deobfuscated) data
100
+ * @category ARID Derivation
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * const obfuscated = obfuscateWithArid(arid, plaintext);
105
+ * const deobfuscated = obfuscateWithArid(arid, obfuscated);
106
+ * // deobfuscated === plaintext
107
+ * ```
108
+ */
109
+ function obfuscateWithArid(arid, data) {
110
+ if (data.length === 0) return new Uint8Array(0);
111
+ const key = deriveKey(OBFUSCATION_SALT, arid, 32);
112
+ const iv = new Uint8Array(12);
113
+ for (let i = 0; i < 12; i++) iv[i] = key[31 - i];
114
+ return chacha20(key, iv, data);
115
+ }
116
+ /**
117
+ * Convert bytes to lowercase hex string.
118
+ * @internal
119
+ */
120
+ function bytesToHex(bytes) {
121
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
122
+ }
123
+
124
+ //#endregion
125
+ export { obfuscateWithArid as i, deriveKey as n, deriveMainlineKey as r, deriveIpfsKeyName as t };
126
+ //# sourceMappingURL=arid-derivation-CbqACjdg.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arid-derivation-CbqACjdg.mjs","names":[],"sources":["../src/arid-derivation.ts"],"sourcesContent":["/**\n * ARID key derivation and obfuscation utilities.\n *\n * Port of arid_derivation.rs from hubert-rust.\n *\n * @module\n */\n\nimport { type ARID } from \"@bcts/components\";\nimport { hkdfHmacSha256 } from \"@bcts/crypto\";\nimport { chacha20 } from \"@noble/ciphers/chacha.js\";\n\n/**\n * Derive a deterministic key from an ARID using a specific salt.\n *\n * Uses HKDF to derive key material from the ARID, ensuring that:\n * - Same ARID always produces same key for a given salt\n * - Keys are cryptographically derived (not guessable)\n * - Collision resistance inherited from ARID\n * - No identifying information in the key (fully anonymized)\n *\n * Port of `derive_key()` from arid_derivation.rs lines 8-29.\n *\n * @param salt - Domain-specific salt to ensure different backends derive different keys\n * @param arid - The ARID to derive from\n * @param outputLen - Length of output in bytes (typically 20 or 32)\n * @returns Derived key bytes\n * @category ARID Derivation\n *\n * @example\n * ```typescript\n * const key = deriveKey(new TextEncoder().encode(\"my-salt\"), arid, 32);\n * ```\n */\nexport function deriveKey(salt: Uint8Array, arid: ARID, outputLen: number): Uint8Array {\n const aridBytes = arid.data();\n // Note: @bcts/crypto hkdfHmacSha256 takes (keyMaterial, salt, keyLen)\n // Rust bc-crypto takes (salt, ikm, keyLen)\n return hkdfHmacSha256(aridBytes, salt, outputLen);\n}\n\n/**\n * Salt for IPFS IPNS key derivation.\n * @internal\n */\nconst IPFS_KEY_NAME_SALT = new TextEncoder().encode(\"hubert-ipfs-ipns-v1\");\n\n/**\n * Salt for Mainline DHT key derivation.\n * @internal\n */\nconst MAINLINE_KEY_SALT = new TextEncoder().encode(\"hubert-mainline-dht-v1\");\n\n/**\n * Salt for obfuscation key derivation.\n * @internal\n */\nconst OBFUSCATION_SALT = new TextEncoder().encode(\"hubert-obfuscation-v1\");\n\n/**\n * Derive an IPNS key name from an ARID.\n *\n * Returns a 64-character hex string suitable for use as an IPFS key name.\n *\n * Port of `derive_ipfs_key_name()` from arid_derivation.rs lines 31-37.\n *\n * @param arid - The ARID to derive from\n * @returns 64-character hex string\n * @category ARID Derivation\n *\n * @example\n * ```typescript\n * const keyName = deriveIpfsKeyName(arid);\n * // => \"a1b2c3d4e5f6...\" (64 hex characters)\n * ```\n */\nexport function deriveIpfsKeyName(arid: ARID): string {\n const keyBytes = deriveKey(IPFS_KEY_NAME_SALT, arid, 32);\n return bytesToHex(keyBytes);\n}\n\n/**\n * Derive Mainline DHT key material from an ARID.\n *\n * Returns 20 bytes of key material (SHA-1 compatible length).\n *\n * Port of `derive_mainline_key()` from arid_derivation.rs lines 39-45.\n *\n * @param arid - The ARID to derive from\n * @returns 20 bytes of key material\n * @category ARID Derivation\n *\n * @example\n * ```typescript\n * const key = deriveMainlineKey(arid);\n * // => Uint8Array(20) [...]\n * ```\n */\nexport function deriveMainlineKey(arid: ARID): Uint8Array {\n return deriveKey(MAINLINE_KEY_SALT, arid, 20);\n}\n\n/**\n * Obfuscate or deobfuscate data using ChaCha20 with an ARID-derived key.\n *\n * This function uses ChaCha20 as a stream cipher to XOR the data with a\n * keystream derived from the ARID. Since XOR is symmetric, the same function\n * is used for both obfuscation and deobfuscation.\n *\n * The result appears as uniform random data to anyone who doesn't have the\n * ARID, hiding both the structure and content of the reference envelope.\n *\n * Port of `obfuscate_with_arid()` from arid_derivation.rs lines 47-91.\n *\n * @param arid - The ARID used to derive the obfuscation key\n * @param data - The data to obfuscate or deobfuscate\n * @returns The obfuscated (or deobfuscated) data\n * @category ARID Derivation\n *\n * @example\n * ```typescript\n * const obfuscated = obfuscateWithArid(arid, plaintext);\n * const deobfuscated = obfuscateWithArid(arid, obfuscated);\n * // deobfuscated === plaintext\n * ```\n */\nexport function obfuscateWithArid(arid: ARID, data: Uint8Array): Uint8Array {\n if (data.length === 0) {\n return new Uint8Array(0);\n }\n\n // Derive a 32-byte key from the ARID using HKDF with domain-specific salt\n const key = deriveKey(OBFUSCATION_SALT, arid, 32);\n\n // Derive IV from the key (last 12 bytes, reversed)\n // Match Rust: key.iter().rev().take(12).copied().collect()\n const iv = new Uint8Array(12);\n for (let i = 0; i < 12; i++) {\n iv[i] = key[31 - i];\n }\n\n // Use ChaCha20 to XOR the data\n // @noble/ciphers chacha20 takes (key, nonce, data) and returns encrypted data\n return chacha20(key, iv, data);\n}\n\n/**\n * Convert bytes to lowercase hex string.\n * @internal\n */\nfunction bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,UAAU,MAAkB,MAAY,WAA+B;AAIrF,QAAO,eAHW,KAAK,MAAM,EAGI,MAAM,UAAU;;;;;;AAOnD,MAAM,qBAAqB,IAAI,aAAa,CAAC,OAAO,sBAAsB;;;;;AAM1E,MAAM,oBAAoB,IAAI,aAAa,CAAC,OAAO,yBAAyB;;;;;AAM5E,MAAM,mBAAmB,IAAI,aAAa,CAAC,OAAO,wBAAwB;;;;;;;;;;;;;;;;;;AAmB1E,SAAgB,kBAAkB,MAAoB;AAEpD,QAAO,WADU,UAAU,oBAAoB,MAAM,GAAG,CAC7B;;;;;;;;;;;;;;;;;;;AAoB7B,SAAgB,kBAAkB,MAAwB;AACxD,QAAO,UAAU,mBAAmB,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;AA2B/C,SAAgB,kBAAkB,MAAY,MAA8B;AAC1E,KAAI,KAAK,WAAW,EAClB,QAAO,IAAI,WAAW,EAAE;CAI1B,MAAM,MAAM,UAAU,kBAAkB,MAAM,GAAG;CAIjD,MAAM,KAAK,IAAI,WAAW,GAAG;AAC7B,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,IACtB,IAAG,KAAK,IAAI,KAAK;AAKnB,QAAO,SAAS,KAAK,IAAI,KAAK;;;;;;AAOhC,SAAS,WAAW,OAA2B;AAC7C,QAAO,MAAM,KAAK,MAAM,CACrB,KAAK,MAAM,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CAC3C,KAAK,GAAG"}