@bcts/seedtool-cli 1.0.0-alpha.22 → 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +87 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +20 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +78 -89
- package/dist/index.mjs.map +1 -1
- package/dist/main.mjs +150 -192
- package/dist/main.mjs.map +1 -1
- package/package.json +18 -18
- package/src/cli.ts +13 -5
- package/src/formats/cards.ts +7 -2
- package/src/formats/multipart.ts +5 -1
- package/src/main.ts +103 -54
- package/src/util.ts +28 -8
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as readline from "node:readline";
|
|
2
|
+
import * as fs from "node:fs";
|
|
2
3
|
import { SSKRGroupSpec, SSKRSecret, SSKRShare, SSKRSpec, Seed as Seed$1, sskrCombine, sskrGenerate } from "@bcts/components";
|
|
3
4
|
import { Envelope, SymmetricKey } from "@bcts/envelope";
|
|
4
5
|
import { DATE, NAME, NOTE, SEED_TYPE } from "@bcts/known-values";
|
|
@@ -9,7 +10,6 @@ import { entropyToMnemonic, mnemonicToEntropy, validateMnemonic } from "@scure/b
|
|
|
9
10
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
10
11
|
import { BytewordsStyle, MultipartDecoder, MultipartEncoder, UR, decodeBytewords, encodeBytewords } from "@bcts/uniform-resources";
|
|
11
12
|
import { SSKR_SHARE, SSKR_SHARE_V1 } from "@bcts/tags";
|
|
12
|
-
|
|
13
13
|
//#region src/cli.ts
|
|
14
14
|
/**
|
|
15
15
|
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
@@ -135,9 +135,9 @@ var Cli = class Cli {
|
|
|
135
135
|
/** The number of output units (hex bytes, base-10 digits, etc.) */
|
|
136
136
|
count = 16;
|
|
137
137
|
/** The input format. Default: Random */
|
|
138
|
-
in =
|
|
138
|
+
in = "random";
|
|
139
139
|
/** The output format. Default: Hex */
|
|
140
|
-
out =
|
|
140
|
+
out = "hex";
|
|
141
141
|
/** The lowest int returned (0-254). Default: 0 */
|
|
142
142
|
low = 0;
|
|
143
143
|
/** The highest int returned (1-255), low < high. Default: 9 */
|
|
@@ -157,7 +157,7 @@ var Cli = class Cli {
|
|
|
157
157
|
/** The number of groups that must meet their threshold. Default: 1 */
|
|
158
158
|
groupThreshold = 1;
|
|
159
159
|
/** SSKR output format. Default: Envelope */
|
|
160
|
-
sskrFormat =
|
|
160
|
+
sskrFormat = "envelope";
|
|
161
161
|
/** Deterministic RNG seed string. */
|
|
162
162
|
deterministic;
|
|
163
163
|
/** The seed being processed (internal state). */
|
|
@@ -165,12 +165,21 @@ var Cli = class Cli {
|
|
|
165
165
|
/** The RNG source (internal state). */
|
|
166
166
|
rng;
|
|
167
167
|
/**
|
|
168
|
-
* Get input from argument or read from stdin.
|
|
169
|
-
*
|
|
168
|
+
* Get input from argument or lazily read it from stdin.
|
|
169
|
+
*
|
|
170
|
+
* Mirrors Rust's `Cli::expect_input` (seedtool-cli-rust/src/cli.rs:191-200):
|
|
171
|
+
* stdin is touched only when an input-consuming code path actually needs it.
|
|
172
|
+
* Modes like `--in random` or `-d <SEED>` (deterministic) never call this,
|
|
173
|
+
* so they exit cleanly even when stdin is a non-TTY but no input is piped.
|
|
174
|
+
*
|
|
175
|
+
* Reads the stdin file descriptor synchronously via `fs.readFileSync(0, ...)`
|
|
176
|
+
* and caches the trimmed result on `this.input` so subsequent calls don't
|
|
177
|
+
* re-read.
|
|
170
178
|
*/
|
|
171
179
|
expectInput() {
|
|
172
180
|
if (this.input !== void 0) return this.input;
|
|
173
|
-
|
|
181
|
+
this.input = fs.readFileSync(0, "utf-8").trim();
|
|
182
|
+
return this.input;
|
|
174
183
|
}
|
|
175
184
|
/**
|
|
176
185
|
* Get input from argument or read from stdin asynchronously.
|
|
@@ -263,7 +272,6 @@ var Cli = class Cli {
|
|
|
263
272
|
return cli;
|
|
264
273
|
}
|
|
265
274
|
};
|
|
266
|
-
|
|
267
275
|
//#endregion
|
|
268
276
|
//#region src/seed.ts
|
|
269
277
|
/**
|
|
@@ -471,7 +479,6 @@ var Seed = class Seed {
|
|
|
471
479
|
return true;
|
|
472
480
|
}
|
|
473
481
|
};
|
|
474
|
-
|
|
475
482
|
//#endregion
|
|
476
483
|
//#region src/random.ts
|
|
477
484
|
/**
|
|
@@ -579,7 +586,6 @@ function sha256DeterministicRandomString(str, n) {
|
|
|
579
586
|
function deterministicRandom(entropy, n) {
|
|
580
587
|
return hkdfHmacSha256(sha256(entropy), new Uint8Array(0), n);
|
|
581
588
|
}
|
|
582
|
-
|
|
583
589
|
//#endregion
|
|
584
590
|
//#region src/util.ts
|
|
585
591
|
/**
|
|
@@ -599,18 +605,34 @@ function dataToHex(bytes) {
|
|
|
599
605
|
}
|
|
600
606
|
/**
|
|
601
607
|
* Convert hex string to bytes.
|
|
602
|
-
*
|
|
608
|
+
*
|
|
609
|
+
* Mirrors the error wording produced by Rust's `hex` crate
|
|
610
|
+
* (`FromHexError::OddLength` and `FromHexError::InvalidHexCharacter`),
|
|
611
|
+
* which seedtool-cli-rust surfaces unchanged through anyhow:
|
|
612
|
+
*
|
|
613
|
+
* "Odd number of digits"
|
|
614
|
+
* "Invalid character '{c}' at position {n}"
|
|
615
|
+
*
|
|
616
|
+
* The outer CLI layer adds the `Error: ` prefix to match Rust's anyhow
|
|
617
|
+
* output.
|
|
603
618
|
*/
|
|
604
619
|
function hexToData(hex) {
|
|
605
|
-
if (hex.length % 2 !== 0) throw new Error("
|
|
620
|
+
if (hex.length % 2 !== 0) throw new Error("Odd number of digits");
|
|
606
621
|
const bytes = new Uint8Array(hex.length / 2);
|
|
607
622
|
for (let i = 0; i < hex.length; i += 2) {
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
bytes[i / 2] =
|
|
623
|
+
const hi = hexCharToNibble(hex, i);
|
|
624
|
+
const lo = hexCharToNibble(hex, i + 1);
|
|
625
|
+
bytes[i / 2] = hi << 4 | lo;
|
|
611
626
|
}
|
|
612
627
|
return bytes;
|
|
613
628
|
}
|
|
629
|
+
function hexCharToNibble(hex, index) {
|
|
630
|
+
const c = hex.charCodeAt(index);
|
|
631
|
+
if (c >= 48 && c <= 57) return c - 48;
|
|
632
|
+
if (c >= 97 && c <= 102) return c - 97 + 10;
|
|
633
|
+
if (c >= 65 && c <= 70) return c - 65 + 10;
|
|
634
|
+
throw new Error(`Invalid character '${hex[index]}' at position ${index}`);
|
|
635
|
+
}
|
|
614
636
|
/**
|
|
615
637
|
* Convert byte values to a different base range [0, base-1].
|
|
616
638
|
* Each byte (0-255) is scaled proportionally to the target base.
|
|
@@ -688,12 +710,11 @@ function digitsToData(inStr, low, high) {
|
|
|
688
710
|
const result = [];
|
|
689
711
|
for (const c of inStr) {
|
|
690
712
|
const n = c.charCodeAt(0) - "0".charCodeAt(0);
|
|
691
|
-
if (n < low || n > high) throw new Error(
|
|
713
|
+
if (n < low || n > high) throw new Error("Invalid digit.");
|
|
692
714
|
result.push(n);
|
|
693
715
|
}
|
|
694
716
|
return new Uint8Array(result);
|
|
695
717
|
}
|
|
696
|
-
|
|
697
718
|
//#endregion
|
|
698
719
|
//#region src/formats/hex.ts
|
|
699
720
|
/**
|
|
@@ -716,7 +737,6 @@ var HexFormat = class {
|
|
|
716
737
|
return dataToHex(state.expectSeed().data());
|
|
717
738
|
}
|
|
718
739
|
};
|
|
719
|
-
|
|
720
740
|
//#endregion
|
|
721
741
|
//#region src/formats/bip39.ts
|
|
722
742
|
/**
|
|
@@ -741,18 +761,9 @@ var Bip39Format = class {
|
|
|
741
761
|
return entropyToMnemonic(state.expectSeed().data(), wordlist);
|
|
742
762
|
}
|
|
743
763
|
};
|
|
744
|
-
|
|
745
764
|
//#endregion
|
|
746
765
|
//#region src/formats/sskr.ts
|
|
747
766
|
/**
|
|
748
|
-
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
749
|
-
* Copyright © 2025-2026 Parity Technologies
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
* SSKR format
|
|
753
|
-
* Ported from seedtool-cli-rust/src/formats/sskr.rs
|
|
754
|
-
*/
|
|
755
|
-
/**
|
|
756
767
|
* SSKR format handler.
|
|
757
768
|
* Round-trippable: sskr shares → seed → sskr shares.
|
|
758
769
|
* Supports multiple sub-formats: envelope, btw, btwm, btwu, ur.
|
|
@@ -777,15 +788,15 @@ var SSKRFormat = class {
|
|
|
777
788
|
};
|
|
778
789
|
function outputSskrSeed(seed, spec, format) {
|
|
779
790
|
switch (format) {
|
|
780
|
-
case
|
|
791
|
+
case "envelope": {
|
|
781
792
|
const seedEnvelope = seed.toEnvelope();
|
|
782
793
|
const contentKey = SymmetricKey.new();
|
|
783
794
|
return seedEnvelope.wrap().encryptSubject(contentKey).sskrSplitFlattened(spec, contentKey).map((env) => env.urString()).join("\n");
|
|
784
795
|
}
|
|
785
|
-
case
|
|
786
|
-
case
|
|
787
|
-
case
|
|
788
|
-
case
|
|
796
|
+
case "btw": return makeBytewordsShares(spec, seed, BytewordsStyle.Standard);
|
|
797
|
+
case "btwm": return makeBytewordsShares(spec, seed, BytewordsStyle.Minimal);
|
|
798
|
+
case "btwu": return makeBytewordsShares(spec, seed, BytewordsStyle.Uri);
|
|
799
|
+
case "ur": return makeShares(spec, seed).map((share) => {
|
|
789
800
|
return UR.new("sskr", toByteString(share.asBytes())).toString();
|
|
790
801
|
}).join("\n");
|
|
791
802
|
}
|
|
@@ -895,7 +906,6 @@ function parseSskrSeed(input) {
|
|
|
895
906
|
if (urLegacyResult !== null) return urLegacyResult;
|
|
896
907
|
throw new Error("Insufficient or invalid SSKR shares.");
|
|
897
908
|
}
|
|
898
|
-
|
|
899
909
|
//#endregion
|
|
900
910
|
//#region src/formats/envelope.ts
|
|
901
911
|
/**
|
|
@@ -919,7 +929,6 @@ var EnvelopeFormat = class {
|
|
|
919
929
|
return state.toEnvelope().urString();
|
|
920
930
|
}
|
|
921
931
|
};
|
|
922
|
-
|
|
923
932
|
//#endregion
|
|
924
933
|
//#region src/formats/seed-format.ts
|
|
925
934
|
/**
|
|
@@ -944,7 +953,6 @@ var SeedFormat = class {
|
|
|
944
953
|
return state.seedWithOverrides().toComponentsSeed().urString();
|
|
945
954
|
}
|
|
946
955
|
};
|
|
947
|
-
|
|
948
956
|
//#endregion
|
|
949
957
|
//#region src/formats/multipart.ts
|
|
950
958
|
/**
|
|
@@ -966,7 +974,7 @@ var MultipartFormat = class {
|
|
|
966
974
|
decoder.receive(share);
|
|
967
975
|
if (decoder.isComplete()) break;
|
|
968
976
|
}
|
|
969
|
-
if (!decoder.isComplete()) throw new Error("Insufficient
|
|
977
|
+
if (!decoder.isComplete()) throw new Error("Insufficient SSKR shares");
|
|
970
978
|
const ur = decoder.message();
|
|
971
979
|
if (ur === void 0 || ur === null) throw new Error("Failed to decode multipart message");
|
|
972
980
|
const envelope = Envelope.fromUR(ur);
|
|
@@ -981,7 +989,6 @@ var MultipartFormat = class {
|
|
|
981
989
|
return parts.join("\n");
|
|
982
990
|
}
|
|
983
991
|
};
|
|
984
|
-
|
|
985
992
|
//#endregion
|
|
986
993
|
//#region src/formats/random.ts
|
|
987
994
|
/**
|
|
@@ -1001,7 +1008,6 @@ var RandomFormat = class {
|
|
|
1001
1008
|
return state;
|
|
1002
1009
|
}
|
|
1003
1010
|
};
|
|
1004
|
-
|
|
1005
1011
|
//#endregion
|
|
1006
1012
|
//#region src/formats/base6.ts
|
|
1007
1013
|
/**
|
|
@@ -1027,7 +1033,6 @@ var Base6Format = class {
|
|
|
1027
1033
|
return dataToInts(state.expectSeed().data(), 0, 5, "");
|
|
1028
1034
|
}
|
|
1029
1035
|
};
|
|
1030
|
-
|
|
1031
1036
|
//#endregion
|
|
1032
1037
|
//#region src/formats/base10.ts
|
|
1033
1038
|
/**
|
|
@@ -1053,7 +1058,6 @@ var Base10Format = class {
|
|
|
1053
1058
|
return dataToInts(state.expectSeed().data(), 0, 9, "");
|
|
1054
1059
|
}
|
|
1055
1060
|
};
|
|
1056
|
-
|
|
1057
1061
|
//#endregion
|
|
1058
1062
|
//#region src/formats/bits.ts
|
|
1059
1063
|
/**
|
|
@@ -1079,7 +1083,6 @@ var BitsFormat = class {
|
|
|
1079
1083
|
return dataToInts(state.expectSeed().data(), 0, 1, "");
|
|
1080
1084
|
}
|
|
1081
1085
|
};
|
|
1082
|
-
|
|
1083
1086
|
//#endregion
|
|
1084
1087
|
//#region src/formats/dice.ts
|
|
1085
1088
|
/**
|
|
@@ -1105,7 +1108,6 @@ var DiceFormat = class {
|
|
|
1105
1108
|
return dataToInts(state.expectSeed().data(), 1, 6, "");
|
|
1106
1109
|
}
|
|
1107
1110
|
};
|
|
1108
|
-
|
|
1109
1111
|
//#endregion
|
|
1110
1112
|
//#region src/formats/cards.ts
|
|
1111
1113
|
const CARD_SUITS = "cdhs";
|
|
@@ -1117,7 +1119,7 @@ const CARD_RANKS = "a23456789tjqk";
|
|
|
1117
1119
|
function parseRank(c) {
|
|
1118
1120
|
const lower = c.toLowerCase();
|
|
1119
1121
|
const index = CARD_RANKS.indexOf(lower);
|
|
1120
|
-
if (index === -1) throw new Error(
|
|
1122
|
+
if (index === -1) throw new Error("Invalid card rank. Allowed: [A,2-9,T,J,Q,K]");
|
|
1121
1123
|
return index;
|
|
1122
1124
|
}
|
|
1123
1125
|
/**
|
|
@@ -1127,7 +1129,7 @@ function parseRank(c) {
|
|
|
1127
1129
|
function parseSuit(c) {
|
|
1128
1130
|
const lower = c.toLowerCase();
|
|
1129
1131
|
const index = CARD_SUITS.indexOf(lower);
|
|
1130
|
-
if (index === -1) throw new Error(
|
|
1132
|
+
if (index === -1) throw new Error("Invalid card rank. Allowed: [D,C,H,S]");
|
|
1131
1133
|
return index;
|
|
1132
1134
|
}
|
|
1133
1135
|
/**
|
|
@@ -1175,7 +1177,6 @@ var CardsFormat = class {
|
|
|
1175
1177
|
return dataToAlphabet(state.expectSeed().data(), 52, toCard);
|
|
1176
1178
|
}
|
|
1177
1179
|
};
|
|
1178
|
-
|
|
1179
1180
|
//#endregion
|
|
1180
1181
|
//#region src/formats/ints.ts
|
|
1181
1182
|
/**
|
|
@@ -1199,7 +1200,6 @@ var IntsFormat = class {
|
|
|
1199
1200
|
return dataToInts(state.expectSeed().data(), state.low, state.high, " ");
|
|
1200
1201
|
}
|
|
1201
1202
|
};
|
|
1202
|
-
|
|
1203
1203
|
//#endregion
|
|
1204
1204
|
//#region src/formats/bytewords-standard.ts
|
|
1205
1205
|
/**
|
|
@@ -1223,7 +1223,6 @@ var BytewordsStandardFormat = class {
|
|
|
1223
1223
|
return encodeBytewords(state.expectSeed().data(), BytewordsStyle.Standard);
|
|
1224
1224
|
}
|
|
1225
1225
|
};
|
|
1226
|
-
|
|
1227
1226
|
//#endregion
|
|
1228
1227
|
//#region src/formats/bytewords-minimal.ts
|
|
1229
1228
|
/**
|
|
@@ -1247,7 +1246,6 @@ var BytewordsMinimalFormat = class {
|
|
|
1247
1246
|
return encodeBytewords(state.expectSeed().data(), BytewordsStyle.Minimal);
|
|
1248
1247
|
}
|
|
1249
1248
|
};
|
|
1250
|
-
|
|
1251
1249
|
//#endregion
|
|
1252
1250
|
//#region src/formats/bytewords-uri.ts
|
|
1253
1251
|
/**
|
|
@@ -1271,39 +1269,30 @@ var BytewordsUriFormat = class {
|
|
|
1271
1269
|
return encodeBytewords(state.expectSeed().data(), BytewordsStyle.Uri);
|
|
1272
1270
|
}
|
|
1273
1271
|
};
|
|
1274
|
-
|
|
1275
1272
|
//#endregion
|
|
1276
1273
|
//#region src/formats/format.ts
|
|
1277
1274
|
/**
|
|
1278
|
-
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
1279
|
-
* Copyright © 2025-2026 Parity Technologies
|
|
1280
|
-
*
|
|
1281
|
-
*
|
|
1282
|
-
* Format traits and factory functions
|
|
1283
|
-
* Ported from seedtool-cli-rust/src/formats/format.rs
|
|
1284
|
-
*/
|
|
1285
|
-
/**
|
|
1286
1275
|
* Select input format by key.
|
|
1287
1276
|
* Matches Rust select_input_format function.
|
|
1288
1277
|
*/
|
|
1289
1278
|
function selectInputFormat(key) {
|
|
1290
1279
|
switch (key) {
|
|
1291
|
-
case
|
|
1292
|
-
case
|
|
1293
|
-
case
|
|
1294
|
-
case
|
|
1295
|
-
case
|
|
1296
|
-
case
|
|
1297
|
-
case
|
|
1298
|
-
case
|
|
1299
|
-
case
|
|
1300
|
-
case
|
|
1301
|
-
case
|
|
1302
|
-
case
|
|
1303
|
-
case
|
|
1304
|
-
case
|
|
1305
|
-
case
|
|
1306
|
-
case
|
|
1280
|
+
case "random": return new RandomFormat();
|
|
1281
|
+
case "hex": return new HexFormat();
|
|
1282
|
+
case "btw": return new BytewordsStandardFormat();
|
|
1283
|
+
case "btwu": return new BytewordsUriFormat();
|
|
1284
|
+
case "btwm": return new BytewordsMinimalFormat();
|
|
1285
|
+
case "bits": return new BitsFormat();
|
|
1286
|
+
case "cards": return new CardsFormat();
|
|
1287
|
+
case "dice": return new DiceFormat();
|
|
1288
|
+
case "base6": return new Base6Format();
|
|
1289
|
+
case "base10": return new Base10Format();
|
|
1290
|
+
case "ints": return new IntsFormat();
|
|
1291
|
+
case "bip39": return new Bip39Format();
|
|
1292
|
+
case "sskr": return new SSKRFormat();
|
|
1293
|
+
case "envelope": return new EnvelopeFormat();
|
|
1294
|
+
case "multipart": return new MultipartFormat();
|
|
1295
|
+
case "seed": return new SeedFormat();
|
|
1307
1296
|
}
|
|
1308
1297
|
}
|
|
1309
1298
|
/**
|
|
@@ -1312,24 +1301,24 @@ function selectInputFormat(key) {
|
|
|
1312
1301
|
*/
|
|
1313
1302
|
function selectOutputFormat(key) {
|
|
1314
1303
|
switch (key) {
|
|
1315
|
-
case
|
|
1316
|
-
case
|
|
1317
|
-
case
|
|
1318
|
-
case
|
|
1319
|
-
case
|
|
1320
|
-
case
|
|
1321
|
-
case
|
|
1322
|
-
case
|
|
1323
|
-
case
|
|
1324
|
-
case
|
|
1325
|
-
case
|
|
1326
|
-
case
|
|
1327
|
-
case
|
|
1328
|
-
case
|
|
1329
|
-
case
|
|
1304
|
+
case "hex": return new HexFormat();
|
|
1305
|
+
case "btw": return new BytewordsStandardFormat();
|
|
1306
|
+
case "btwu": return new BytewordsUriFormat();
|
|
1307
|
+
case "btwm": return new BytewordsMinimalFormat();
|
|
1308
|
+
case "bits": return new BitsFormat();
|
|
1309
|
+
case "cards": return new CardsFormat();
|
|
1310
|
+
case "dice": return new DiceFormat();
|
|
1311
|
+
case "base6": return new Base6Format();
|
|
1312
|
+
case "base10": return new Base10Format();
|
|
1313
|
+
case "ints": return new IntsFormat();
|
|
1314
|
+
case "bip39": return new Bip39Format();
|
|
1315
|
+
case "sskr": return new SSKRFormat();
|
|
1316
|
+
case "envelope": return new EnvelopeFormat();
|
|
1317
|
+
case "multipart": return new MultipartFormat();
|
|
1318
|
+
case "seed": return new SeedFormat();
|
|
1330
1319
|
}
|
|
1331
1320
|
}
|
|
1332
|
-
|
|
1333
1321
|
//#endregion
|
|
1334
1322
|
export { Base10Format, Base6Format, Bip39Format, BitsFormat, BytewordsMinimalFormat, BytewordsStandardFormat, BytewordsUriFormat, CardsFormat, Cli, DeterministicRandomNumberGenerator, DiceFormat, EnvelopeFormat, HexFormat, InputFormatKey, IntsFormat, MultipartFormat, OutputFormatKey, RandomFormat, SSKRFormat, SSKRFormatKey, Seed, SeedFormat, dataToAlphabet, dataToBase, dataToHex, dataToInts, deterministicRandom, digitsToData, hexToData, hkdfHmacSha256, parseDate, parseGroupSpec, parseGroupThreshold, parseHighInt, parseInts, parseLowInt, selectInputFormat, selectOutputFormat, sha256DeterministicRandom };
|
|
1323
|
+
|
|
1335
1324
|
//# sourceMappingURL=index.mjs.map
|