@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.
- package/LICENSE +48 -0
- package/README.md +18 -0
- package/dist/arid-derivation-1CJuU-kZ.cjs +150 -0
- package/dist/arid-derivation-1CJuU-kZ.cjs.map +1 -0
- package/dist/arid-derivation-CbqACjdg.mjs +126 -0
- package/dist/arid-derivation-CbqACjdg.mjs.map +1 -0
- package/dist/bin/hubert.cjs +384 -0
- package/dist/bin/hubert.cjs.map +1 -0
- package/dist/bin/hubert.d.cts +1 -0
- package/dist/bin/hubert.d.mts +1 -0
- package/dist/bin/hubert.mjs +383 -0
- package/dist/bin/hubert.mjs.map +1 -0
- package/dist/chunk-CbDLau6x.cjs +34 -0
- package/dist/hybrid/index.cjs +14 -0
- package/dist/hybrid/index.d.cts +3 -0
- package/dist/hybrid/index.d.mts +3 -0
- package/dist/hybrid/index.mjs +6 -0
- package/dist/hybrid-BZhumygj.mjs +356 -0
- package/dist/hybrid-BZhumygj.mjs.map +1 -0
- package/dist/hybrid-dX5JLumO.cjs +410 -0
- package/dist/hybrid-dX5JLumO.cjs.map +1 -0
- package/dist/index-BEzpUC7r.d.mts +380 -0
- package/dist/index-BEzpUC7r.d.mts.map +1 -0
- package/dist/index-C2F6ugLL.d.mts +210 -0
- package/dist/index-C2F6ugLL.d.mts.map +1 -0
- package/dist/index-CUnDouMb.d.mts +215 -0
- package/dist/index-CUnDouMb.d.mts.map +1 -0
- package/dist/index-CV6lZJqY.d.cts +380 -0
- package/dist/index-CV6lZJqY.d.cts.map +1 -0
- package/dist/index-CY3TCzIm.d.cts +217 -0
- package/dist/index-CY3TCzIm.d.cts.map +1 -0
- package/dist/index-DEr4SR1J.d.cts +215 -0
- package/dist/index-DEr4SR1J.d.cts.map +1 -0
- package/dist/index-T1LHanIb.d.mts +217 -0
- package/dist/index-T1LHanIb.d.mts.map +1 -0
- package/dist/index-jyzuOhFB.d.cts +210 -0
- package/dist/index-jyzuOhFB.d.cts.map +1 -0
- package/dist/index.cjs +60 -0
- package/dist/index.d.cts +161 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +161 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +10 -0
- package/dist/ipfs/index.cjs +13 -0
- package/dist/ipfs/index.d.cts +3 -0
- package/dist/ipfs/index.d.mts +3 -0
- package/dist/ipfs/index.mjs +5 -0
- package/dist/ipfs-BRMMCBjv.mjs +1 -0
- package/dist/ipfs-CetOVQcO.cjs +0 -0
- package/dist/kv-BAmhmMOo.cjs +425 -0
- package/dist/kv-BAmhmMOo.cjs.map +1 -0
- package/dist/kv-C-emxv0w.mjs +375 -0
- package/dist/kv-C-emxv0w.mjs.map +1 -0
- package/dist/kv-DJiKvypY.mjs +403 -0
- package/dist/kv-DJiKvypY.mjs.map +1 -0
- package/dist/kv-store-DmngWWuw.d.mts +183 -0
- package/dist/kv-store-DmngWWuw.d.mts.map +1 -0
- package/dist/kv-store-ww-AUyLd.d.cts +183 -0
- package/dist/kv-store-ww-AUyLd.d.cts.map +1 -0
- package/dist/kv-yjvQa_LH.cjs +457 -0
- package/dist/kv-yjvQa_LH.cjs.map +1 -0
- package/dist/logging-hmzNzifq.mjs +158 -0
- package/dist/logging-hmzNzifq.mjs.map +1 -0
- package/dist/logging-qc9uMgil.cjs +212 -0
- package/dist/logging-qc9uMgil.cjs.map +1 -0
- package/dist/mainline/index.cjs +12 -0
- package/dist/mainline/index.d.cts +3 -0
- package/dist/mainline/index.d.mts +3 -0
- package/dist/mainline/index.mjs +5 -0
- package/dist/mainline-D_jfeFMh.cjs +0 -0
- package/dist/mainline-cFIuXbo-.mjs +1 -0
- package/dist/server/index.cjs +14 -0
- package/dist/server/index.d.cts +3 -0
- package/dist/server/index.d.mts +3 -0
- package/dist/server/index.mjs +3 -0
- package/dist/server-BBNRZ30D.cjs +912 -0
- package/dist/server-BBNRZ30D.cjs.map +1 -0
- package/dist/server-DVyk9gqU.mjs +836 -0
- package/dist/server-DVyk9gqU.mjs.map +1 -0
- package/package.json +125 -0
- package/src/arid-derivation.ts +155 -0
- package/src/bin/hubert.ts +667 -0
- package/src/error.ts +89 -0
- package/src/hybrid/error.ts +77 -0
- package/src/hybrid/index.ts +24 -0
- package/src/hybrid/kv.ts +236 -0
- package/src/hybrid/reference.ts +176 -0
- package/src/index.ts +145 -0
- package/src/ipfs/error.ts +83 -0
- package/src/ipfs/index.ts +24 -0
- package/src/ipfs/kv.ts +476 -0
- package/src/ipfs/value.ts +85 -0
- package/src/kv-store.ts +128 -0
- package/src/logging.ts +88 -0
- package/src/mainline/error.ts +108 -0
- package/src/mainline/index.ts +23 -0
- package/src/mainline/kv.ts +411 -0
- package/src/server/error.ts +83 -0
- package/src/server/index.ts +29 -0
- package/src/server/kv.ts +211 -0
- package/src/server/memory-kv.ts +191 -0
- package/src/server/server-kv.ts +92 -0
- package/src/server/server.ts +369 -0
- 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"}
|