Compare commits

..

11 Commits

34 changed files with 918 additions and 393 deletions

341
Cargo.lock generated
View File

@ -335,12 +335,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.22.0" version = "0.22.0"
@ -516,9 +510,9 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.14.1" version = "1.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2490600f404f2b94c167e31d3ed1d5f3c225a0f3b80230053b3e0b7b962bd9" checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -841,16 +835,15 @@ dependencies = [
[[package]] [[package]]
name = "curve25519-dalek" name = "curve25519-dalek"
version = "4.1.1" version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
"curve25519-dalek-derive", "curve25519-dalek-derive",
"digest", "digest",
"fiat-crypto", "fiat-crypto",
"platforms",
"rustc_version", "rustc_version",
"subtle", "subtle",
"zeroize", "zeroize",
@ -959,6 +952,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]] [[package]]
name = "dsa" name = "dsa"
version = "0.6.2" version = "0.6.2"
@ -1524,6 +1528,124 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_properties_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]] [[package]]
name = "idea" name = "idea"
version = "0.5.1" version = "0.5.1"
@ -1535,12 +1657,14 @@ dependencies = [
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.4.0" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" checksum = "bd69211b9b519e98303c015e21a007e293db403b6c85b9b124e133d25e242cdd"
dependencies = [ dependencies = [
"unicode-bidi", "icu_normalizer",
"unicode-normalization", "icu_properties",
"smallvec",
"utf8_iter",
] ]
[[package]] [[package]]
@ -1681,9 +1805,10 @@ dependencies = [
"clap_complete", "clap_complete",
"keyfork-bin", "keyfork-bin",
"keyfork-derive-openpgp", "keyfork-derive-openpgp",
"keyfork-derive-path-data",
"keyfork-derive-util", "keyfork-derive-util",
"keyfork-entropy", "keyfork-entropy",
"keyfork-mnemonic-util", "keyfork-mnemonic",
"keyfork-prompt", "keyfork-prompt",
"keyfork-qrcode", "keyfork-qrcode",
"keyfork-shard", "keyfork-shard",
@ -1748,6 +1873,7 @@ version = "0.1.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"ed25519-dalek", "ed25519-dalek",
"keyfork-derive-path-data",
"keyfork-derive-util", "keyfork-derive-util",
"keyforkd-client", "keyforkd-client",
"sequoia-openpgp", "sequoia-openpgp",
@ -1759,6 +1885,7 @@ name = "keyfork-derive-path-data"
version = "0.1.1" version = "0.1.1"
dependencies = [ dependencies = [
"keyfork-derive-util", "keyfork-derive-util",
"once_cell",
] ]
[[package]] [[package]]
@ -1771,7 +1898,7 @@ dependencies = [
"hmac", "hmac",
"k256", "k256",
"keyfork-bug", "keyfork-bug",
"keyfork-mnemonic-util", "keyfork-mnemonic",
"keyfork-slip10-test-data", "keyfork-slip10-test-data",
"ripemd", "ripemd",
"serde", "serde",
@ -1798,7 +1925,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "keyfork-mnemonic-util" name = "keyfork-mnemonic"
version = "0.3.0" version = "0.3.0"
dependencies = [ dependencies = [
"bip39", "bip39",
@ -1817,7 +1944,7 @@ version = "0.1.1"
dependencies = [ dependencies = [
"keyfork-bug", "keyfork-bug",
"keyfork-crossterm", "keyfork-crossterm",
"keyfork-mnemonic-util", "keyfork-mnemonic",
"thiserror", "thiserror",
] ]
@ -1839,13 +1966,13 @@ version = "0.2.2"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"anyhow", "anyhow",
"base64 0.22.0", "base64",
"card-backend", "card-backend",
"card-backend-pcsc", "card-backend-pcsc",
"hkdf", "hkdf",
"keyfork-bug", "keyfork-bug",
"keyfork-derive-openpgp", "keyfork-derive-openpgp",
"keyfork-mnemonic-util", "keyfork-mnemonic",
"keyfork-prompt", "keyfork-prompt",
"keyfork-qrcode", "keyfork-qrcode",
"openpgp-card", "openpgp-card",
@ -1893,7 +2020,7 @@ dependencies = [
"keyfork-derive-path-data", "keyfork-derive-path-data",
"keyfork-derive-util", "keyfork-derive-util",
"keyfork-frame", "keyfork-frame",
"keyfork-mnemonic-util", "keyfork-mnemonic",
"keyfork-slip10-test-data", "keyfork-slip10-test-data",
"keyforkd-models", "keyforkd-models",
"serde", "serde",
@ -2033,6 +2160,12 @@ version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "litemap"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.11" version = "0.4.11"
@ -2337,6 +2470,32 @@ dependencies = [
"sha2", "sha2",
] ]
[[package]]
name = "p384"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209"
dependencies = [
"ecdsa",
"elliptic-curve",
"primeorder",
"sha2",
]
[[package]]
name = "p521"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2"
dependencies = [
"base16ct",
"ecdsa",
"elliptic-curve",
"primeorder",
"rand_core",
"sha2",
]
[[package]] [[package]]
name = "parking" name = "parking"
version = "2.2.0" version = "2.2.0"
@ -2530,12 +2689,6 @@ version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
[[package]]
name = "platforms"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c"
[[package]] [[package]]
name = "polling" name = "polling"
version = "2.8.0" version = "2.8.0"
@ -2885,14 +3038,14 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]] [[package]]
name = "sequoia-openpgp" name = "sequoia-openpgp"
version = "1.17.0" version = "1.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ea026cf8a70d331c742e3ad7e68fd405d0743ff86630fb4334a1bf8d0e194c7" checksum = "13261ee216b44d932ef93b2d4a75d45199bef77864bcc5b77ecfc7bc0ecb02d6"
dependencies = [ dependencies = [
"aes", "aes",
"aes-gcm", "aes-gcm",
"anyhow", "anyhow",
"base64 0.21.7", "base64",
"block-padding", "block-padding",
"blowfish", "blowfish",
"buffered-reader", "buffered-reader",
@ -2927,6 +3080,8 @@ dependencies = [
"openssl", "openssl",
"openssl-sys", "openssl-sys",
"p256", "p256",
"p384",
"p521",
"rand", "rand",
"rand_core", "rand_core",
"regex", "regex",
@ -3163,6 +3318,12 @@ dependencies = [
"der 0.7.8", "der 0.7.8",
] ]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]] [[package]]
name = "string_cache" name = "string_cache"
version = "0.8.7" version = "0.8.7"
@ -3210,6 +3371,17 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "synstructure"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.10.0" version = "3.10.0"
@ -3282,6 +3454,16 @@ dependencies = [
"crunchy", "crunchy",
] ]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -3443,12 +3625,6 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-bidi"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.12"
@ -3486,6 +3662,18 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.1" version = "0.2.1"
@ -3799,6 +3987,18 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]] [[package]]
name = "x25519-dalek" name = "x25519-dalek"
version = "2.0.0" version = "2.0.0"
@ -3817,6 +4017,30 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53be06678ed9e83edb1745eb72efc0bbcd7b5c3c35711a860906aed827a13d61" checksum = "53be06678ed9e83edb1745eb72efc0bbcd7b5c3c35711a860906aed827a13d61"
[[package]]
name = "yoke"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"synstructure",
]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.7.32" version = "0.7.32"
@ -3837,6 +4061,27 @@ dependencies = [
"syn 2.0.48", "syn 2.0.48",
] ]
[[package]]
name = "zerofrom"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"synstructure",
]
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.7.0" version = "1.7.0"
@ -3856,3 +4101,25 @@ dependencies = [
"quote", "quote",
"syn 2.0.48", "syn 2.0.48",
] ]
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]

View File

@ -19,7 +19,7 @@ members = [
"crates/util/keyfork-crossterm", "crates/util/keyfork-crossterm",
"crates/util/keyfork-entropy", "crates/util/keyfork-entropy",
"crates/util/keyfork-frame", "crates/util/keyfork-frame",
"crates/util/keyfork-mnemonic-util", "crates/util/keyfork-mnemonic",
"crates/util/keyfork-prompt", "crates/util/keyfork-prompt",
"crates/util/keyfork-slip10-test-data", "crates/util/keyfork-slip10-test-data",
"crates/util/smex", "crates/util/smex",

View File

@ -1,8 +1,10 @@
//! # The Keyforkd Client //! # The Keyforkd Client
//! //!
//! Keyfork allows securing the master key and highest-level derivation keys by having derivation //! Keyfork allows securing the master key and highest-level derivation keys by having derivation
//! requests performed against a server, "Keyforkd" or the "Keyfork Server". The server is operated //! requests performed against a server, "Keyforkd" or the "Keyfork Server". This allows
//! on a UNIX socket with messages sent using the Keyfork Frame format. //! enforcement of policies, such as requiring at least two leves of a derivation path (for
//! instance, `m/0'` would not be allowed, but `m/0'/0'` would). The server is operated on a UNIX
//! socket with messages sent using the Keyfork Frame format.
//! //!
//! Programs using the Keyfork Client should ensure they are built against a compatible version of //! Programs using the Keyfork Client should ensure they are built against a compatible version of
//! the Keyfork Server. For versions prior to `1.0.0`, all versions within a "minor" version (i.e., //! the Keyfork Server. For versions prior to `1.0.0`, all versions within a "minor" version (i.e.,
@ -10,18 +12,23 @@
//! after `1.0.0`, all versions within a "major" version (i.e., `1.0.0`) will be compatible, but //! after `1.0.0`, all versions within a "major" version (i.e., `1.0.0`) will be compatible, but
//! `1.x.y` will not be compatible with `2.0.0`. //! `1.x.y` will not be compatible with `2.0.0`.
//! //!
//! Presently, the Keyfork server only supports the following requests: //! The Keyfork Client documentation makes extensive use of the `keyforkd::test_util` module.
//! This provides testing infrastructure to set up a temporary Keyfork Daemon. In
//! your code, you should assume the daemon has already been initialized, whether by another
//! process, on another terminal, or some other instance. At no point should a program deriving an
//! "endpoint" key have control over a mnemonic or a seed.
//! //!
//! * Derive Key //! ## Server Requests
//! //!
//! ## Extended Private Keys //! Keyfork is designed as a client-request/server-response model. The client sends a request, such
//! as a derivation request, and the server sends its response. Presently, the Keyfork server
//! supports the following requests:
//! //!
//! Keyfork doesn't need to be continuously called once a key has been derived. Once an Extended //! ### Request: Derive Key
//! Private Key (often shortened to "XPrv") has been created, further derivations can be performed. //!
//! The tests for this library ensure that all levels of Keyfork derivation beyond the required two //! The client creates a derivation path of at least two indices and requests a derived XPrv
//! will be derived similarly between the server and the client. //! (Extended Private Key) from the server.
//! //!
//! # Examples
//! ```rust //! ```rust
//! use std::str::FromStr; //! use std::str::FromStr;
//! //!
@ -31,17 +38,121 @@
//! // use k256::SecretKey as PrivateKey; //! // use k256::SecretKey as PrivateKey;
//! // use ed25519_dalek::SigningKey as PrivateKey; //! // use ed25519_dalek::SigningKey as PrivateKey;
//! //!
//! #[derive(Debug, thiserror::Error)]
//! enum Error {
//! #[error(transparent)]
//! Path(#[from] keyfork_derive_util::PathError),
//!
//! #[error(transparent)]
//! Keyforkd(#[from] keyforkd_client::Error),
//! }
//!
//! fn main() -> Result<(), Error> {
//! # let seed = b"funky accordion noises"; //! # let seed = b"funky accordion noises";
//! # keyforkd::test_util::run_test(seed, |socket_path| { //! # keyforkd::test_util::run_test(seed, |socket_path| {
//! # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path); //! let derivation_path = DerivationPath::from_str("m/44'/0'")?;
//! let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap(); //! let mut client = Client::discover_socket()?;
//! let mut client = Client::discover_socket().unwrap(); //! let xprv = client.request_xprv::<PrivateKey>(&derivation_path)?;
//! let xprv = client.request_xprv::<PrivateKey>(&derivation_path).unwrap(); //! # Ok::<_, Error>(())
//! # keyforkd::test_util::Infallible::Ok(()) //! # })?;
//! # }).unwrap(); //! Ok(())
//! }
//! ``` //! ```
//! //!
//! In tests, the Keyforkd test_util module and TestPrivateKeys can be used. //! ---
//!
//! Request objects are typically handled by the Keyfork Client library (such as with
//! [`Client::request_xprv`]). While unadvised, clients can also attempt to handle their own
//! requests, using [`Client::request`].
//!
//! ## Extended Private Keys
//!
//! Keyfork doesn't need to be continuously called once a key has been derived. Once an Extended
//! Private Key (often shortened to "XPrv") has been created, further derivations can be performed.
//! The tests for this library ensure that all levels of Keyfork derivation beyond the required two
//! will be derived similarly between the server and the client.
//!
//! ```rust
//! use std::str::FromStr;
//!
//! use keyforkd_client::Client;
//! use keyfork_derive_util::{DerivationIndex, DerivationPath};
//! # use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey;
//! // use k256::SecretKey as PrivateKey;
//! // use ed25519_dalek::SigningKey as PrivateKey;
//! # fn check_wallet<T>(_: T) {}
//!
//! #[derive(Debug, thiserror::Error)]
//! enum Error {
//! #[error(transparent)]
//! Index(#[from] keyfork_derive_util::IndexError),
//!
//! #[error(transparent)]
//! Path(#[from] keyfork_derive_util::PathError),
//!
//! #[error(transparent)]
//! PrivateKey(#[from] keyfork_derive_util::PrivateKeyError),
//!
//! #[error(transparent)]
//! Keyforkd(#[from] keyforkd_client::Error),
//! }
//!
//! fn main() -> Result<(), Error> {
//! # let seed = b"funky accordion noises";
//! # keyforkd::test_util::run_test(seed, |socket_path| {
//! let derivation_path = DerivationPath::from_str("m/44'/0'/0'/0")?;
//! let mut client = Client::discover_socket()?;
//! let xprv = client.request_xprv::<PrivateKey>(&derivation_path)?;
//! // scan first 20 wallets
//! for index in 0..20 {
//! // use non-hardened derivation
//! let new_xprv = xprv.derive_child(&DerivationIndex::new(index, false)?);
//! check_wallet(new_xprv)
//! }
//! # Ok::<_, Error>(())
//! # })?;
//! Ok(())
//! }
//! ```
//!
//! ## Testing Infrastructure
//!
//! In tests, the `keyforkd::test_util` module and TestPrivateKeys can be used. These provide
//! useful utilities for writing tests that interact with the Keyfork Server without needing to
//! manually create the server for the purpose of the test. The `run_test` method can be used to
//! run a test, which can handle both returning errors and correctly translating panics (though,
//! the panics definitely won't look tidy).
//!
//! ```rust
//! use std::str::FromStr;
//!
//! use keyforkd_client::Client;
//! use keyfork_derive_util::DerivationPath;
//! use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey;
//!
//! #[derive(Debug, thiserror::Error)]
//! enum Error {
//! #[error(transparent)]
//! Path(#[from] keyfork_derive_util::PathError),
//!
//! #[error(transparent)]
//! Keyforkd(#[from] keyforkd_client::Error),
//! }
//!
//! fn main() -> Result<(), Error> {
//! let seed = b"funky accordion noises";
//! keyforkd::test_util::run_test(seed, |socket_path| {
//! let derivation_path = DerivationPath::from_str("m/44'/0'")?;
//! let mut client = Client::discover_socket()?;
//! let xprv = client.request_xprv::<PrivateKey>(&derivation_path)?;
//! Ok::<_, Error>(())
//! })?;
//! Ok(())
//! }
//! ```
//!
//! If you would rather write tests to panic rather than error, or would rather not deal with error
//! types, the Panicable type should be used, which will handle the Error type for the closure.
//! //!
//! ```rust //! ```rust
//! use std::str::FromStr; //! use std::str::FromStr;
@ -52,11 +163,10 @@
//! //!
//! let seed = b"funky accordion noises"; //! let seed = b"funky accordion noises";
//! keyforkd::test_util::run_test(seed, |socket_path| { //! keyforkd::test_util::run_test(seed, |socket_path| {
//! std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path);
//! let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap(); //! let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap();
//! let mut client = Client::discover_socket().unwrap(); //! let mut client = Client::discover_socket().unwrap();
//! let xprv = client.request_xprv::<PrivateKey>(&derivation_path).unwrap(); //! let xprv = client.request_xprv::<PrivateKey>(&derivation_path).unwrap();
//! keyforkd::test_util::Infallible::Ok(()) //! keyforkd::test_util::Panicable::Ok(())
//! }).unwrap(); //! }).unwrap();
//! ``` //! ```
@ -165,10 +275,9 @@ impl Client {
/// ///
/// # let seed = b"funky accordion noises"; /// # let seed = b"funky accordion noises";
/// # keyforkd::test_util::run_test(seed, |socket_path| { /// # keyforkd::test_util::run_test(seed, |socket_path| {
/// # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path); /// let mut socket = get_socket()?;
/// let mut socket = get_socket().unwrap();
/// let mut client = Client::new(socket); /// let mut client = Client::new(socket);
/// # keyforkd::test_util::Infallible::Ok(()) /// # Ok::<_, keyforkd_client::Error>(())
/// # }).unwrap(); /// # }).unwrap();
/// ``` /// ```
pub fn new(socket: UnixStream) -> Self { pub fn new(socket: UnixStream) -> Self {
@ -187,9 +296,8 @@ impl Client {
/// ///
/// # let seed = b"funky accordion noises"; /// # let seed = b"funky accordion noises";
/// # keyforkd::test_util::run_test(seed, |socket_path| { /// # keyforkd::test_util::run_test(seed, |socket_path| {
/// # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path); /// let mut client = Client::discover_socket()?;
/// let mut client = Client::discover_socket().unwrap(); /// # Ok::<_, keyforkd_client::Error>(())
/// # keyforkd::test_util::Infallible::Ok(())
/// # }).unwrap(); /// # }).unwrap();
/// ``` /// ```
pub fn discover_socket() -> Result<Self> { pub fn discover_socket() -> Result<Self> {
@ -217,11 +325,10 @@ impl Client {
/// ///
/// # let seed = b"funky accordion noises"; /// # let seed = b"funky accordion noises";
/// # keyforkd::test_util::run_test(seed, |socket_path| { /// # keyforkd::test_util::run_test(seed, |socket_path| {
/// # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path);
/// let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap(); /// let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap();
/// let mut client = Client::discover_socket().unwrap(); /// let mut client = Client::discover_socket().unwrap();
/// let xprv = client.request_xprv::<PrivateKey>(&derivation_path).unwrap(); /// let xprv = client.request_xprv::<PrivateKey>(&derivation_path).unwrap();
/// # keyforkd::test_util::Infallible::Ok(()) /// # keyforkd::test_util::Panicable::Ok(())
/// # }).unwrap(); /// # }).unwrap();
/// ``` /// ```
pub fn request_xprv<K>(&mut self, path: &DerivationPath) -> Result<ExtendedPrivateKey<K>> pub fn request_xprv<K>(&mut self, path: &DerivationPath) -> Result<ExtendedPrivateKey<K>>
@ -244,15 +351,20 @@ impl Client {
_ => Err(Error::InvalidResponse), _ => Err(Error::InvalidResponse),
} }
} }
}
impl Client {
/// Serialize and send a [`Request`] to the server, awaiting a [`Result<Response>`]. /// Serialize and send a [`Request`] to the server, awaiting a [`Result<Response>`].
/// ///
/// This function does not properly assert the association between a request type and a
/// response type, and does not perform any serialization of native objects into Request or
/// Response types, and should only be used when absolutely necessary.
///
/// # Errors /// # Errors
/// An error may be returned if: /// An error may be returned if:
/// * Reading or writing from or to the socket encountered an error. /// * Reading or writing from or to the socket encountered an error.
/// * Bincode could not serialize the request or deserialize the response. /// * Bincode could not serialize the request or deserialize the response.
/// * An error occurred in Keyforkd. /// * An error occurred in Keyforkd.
#[doc(hidden)]
pub fn request(&mut self, req: &Request) -> Result<Response> { pub fn request(&mut self, req: &Request) -> Result<Response> {
try_encode_to(&bincode::serialize(&req)?, &mut self.socket)?; try_encode_to(&bincode::serialize(&req)?, &mut self.socket)?;
let resp = try_decode_from(&mut self.socket)?; let resp = try_decode_from(&mut self.socket)?;

View File

@ -1,7 +1,7 @@
use crate::Client; use crate::Client;
use keyfork_derive_util::{request::*, DerivationPath}; use keyfork_derive_util::{request::*, DerivationPath};
use keyfork_slip10_test_data::test_data; use keyfork_slip10_test_data::test_data;
use keyforkd::test_util::{run_test, Infallible}; use keyforkd::test_util::{run_test, Panicable};
use std::{os::unix::net::UnixStream, str::FromStr}; use std::{os::unix::net::UnixStream, str::FromStr};
#[test] #[test]
@ -109,7 +109,7 @@ fn ed25519_test_suite() {
DerivationResponse::try_from(client.request(&req.into()).unwrap()).unwrap(); DerivationResponse::try_from(client.request(&req.into()).unwrap()).unwrap();
assert_eq!(&response.data, test.private_key.as_slice()); assert_eq!(&response.data, test.private_key.as_slice());
} }
Infallible::Ok(()) Panicable::Ok(())
}) })
.unwrap(); .unwrap();
} }

View File

@ -15,7 +15,7 @@ multithread = ["tokio/rt-multi-thread"]
keyfork-bug = { version = "0.1.0", path = "../../util/keyfork-bug", registry = "distrust" } keyfork-bug = { version = "0.1.0", path = "../../util/keyfork-bug", registry = "distrust" }
keyfork-derive-util = { version = "0.2.0", path = "../../derive/keyfork-derive-util", registry = "distrust" } keyfork-derive-util = { version = "0.2.0", path = "../../derive/keyfork-derive-util", registry = "distrust" }
keyfork-frame = { version = "0.1.0", path = "../../util/keyfork-frame", features = ["async"], registry = "distrust" } keyfork-frame = { version = "0.1.0", path = "../../util/keyfork-frame", features = ["async"], registry = "distrust" }
keyfork-mnemonic-util = { version = "0.3.0", path = "../../util/keyfork-mnemonic-util", registry = "distrust" } keyfork-mnemonic = { version = "0.3.0", path = "../../util/keyfork-mnemonic", registry = "distrust" }
keyfork-derive-path-data = { version = "0.1.0", path = "../../derive/keyfork-derive-path-data", registry = "distrust" } keyfork-derive-path-data = { version = "0.1.0", path = "../../derive/keyfork-derive-path-data", registry = "distrust" }
keyforkd-models = { version = "0.2.0", path = "../keyforkd-models", registry = "distrust" } keyforkd-models = { version = "0.2.0", path = "../keyforkd-models", registry = "distrust" }

View File

@ -5,7 +5,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
pub use keyfork_mnemonic_util::Mnemonic; pub use keyfork_mnemonic::Mnemonic;
pub use tower::ServiceBuilder; pub use tower::ServiceBuilder;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
@ -57,7 +57,7 @@ pub async fn start_and_run_server_on(
let service = ServiceBuilder::new() let service = ServiceBuilder::new()
.layer(middleware::BincodeLayer::new()) .layer(middleware::BincodeLayer::new())
// TODO: passphrase support and/or store passphrase with mnemonic // TODO: passphrase support and/or store passphrase with mnemonic
.service(Keyforkd::new(mnemonic.generate_seed(None))); .service(Keyforkd::new(mnemonic.generate_seed(None).to_vec()));
let mut server = match UnixServer::bind(socket_path) { let mut server = match UnixServer::bind(socket_path) {
Ok(s) => s, Ok(s) => s,

View File

@ -1,6 +1,6 @@
//! //!
use keyfork_mnemonic_util::Mnemonic; use keyfork_mnemonic::Mnemonic;
use tokio::io::{self, AsyncBufReadExt, BufReader}; use tokio::io::{self, AsyncBufReadExt, BufReader};

View File

@ -12,20 +12,21 @@ use keyfork_bug::bug;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
#[error("This error can never be instantiated")] #[error("This error can never be instantiated")]
#[doc(hidden)] #[doc(hidden)]
pub struct InfallibleError { pub enum UninstantiableError {}
protected: (),
}
/// An infallible result. This type can be used to represent a function that should never error. /// A panicable result. This type can be used when a closure chooses to panic instead of
/// returning an error. This doesn't necessarily mean a closure _has_ to panic, and its absence
/// doesn't imply a closure _can't_ panic, but this is a useful utility function for writing tests,
/// to avoid the necessity of making custom error types.
/// ///
/// ```rust /// ```rust
/// use keyforkd::test_util::Infallible; /// use keyforkd::test_util::Panicable;
/// let closure = || { /// let closure = || {
/// Infallible::Ok(()) /// Panicable::Ok(())
/// }; /// };
/// assert!(closure().is_ok()); /// assert!(closure().is_ok());
/// ``` /// ```
pub type Infallible<T> = std::result::Result<T, InfallibleError>; pub type Panicable<T> = std::result::Result<T, UninstantiableError>;
/// Run a test making use of a Keyforkd server. The test may use a seed (the first argument) from a /// Run a test making use of a Keyforkd server. The test may use a seed (the first argument) from a
/// test suite, or (as shown in the example below) a simple seed may be used solely to ensure /// test suite, or (as shown in the example below) a simple seed may be used solely to ensure
@ -39,6 +40,8 @@ pub type Infallible<T> = std::result::Result<T, InfallibleError>;
/// runtime. /// runtime.
/// ///
/// # Examples /// # Examples
/// The test utility provides a socket that can be connected to for deriving keys.
///
/// ```rust /// ```rust
/// use std::os::unix::net::UnixStream; /// use std::os::unix::net::UnixStream;
/// let seed = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// let seed = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
@ -46,6 +49,18 @@ pub type Infallible<T> = std::result::Result<T, InfallibleError>;
/// UnixStream::connect(&path).map(|_| ()) /// UnixStream::connect(&path).map(|_| ())
/// }).unwrap(); /// }).unwrap();
/// ``` /// ```
///
/// The `keyforkd-client` crate uses the `KEYFORKD_SOCKET_PATH` variable to determine the default
/// socket path. The test will export the environment variable so it may be used by default.
///
/// ```rust
/// use std::os::unix::net::UnixStream;
/// let seed = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// keyforkd::test_util::run_test(seed.as_slice(), |path| {
/// assert_eq!(std::env::var_os("KEYFORKD_SOCKET_PATH").unwrap(), path.as_os_str());
/// UnixStream::connect(&path).map(|_| ())
/// }).unwrap();
/// ```
#[allow(clippy::missing_errors_doc)] #[allow(clippy::missing_errors_doc)]
pub fn run_test<F, E>(seed: &[u8], closure: F) -> Result<(), E> pub fn run_test<F, E>(seed: &[u8], closure: F) -> Result<(), E>
where where
@ -82,6 +97,7 @@ where
rx.recv() rx.recv()
.await .await
.expect(bug!("can't receive server start signal from channel")); .expect(bug!("can't receive server start signal from channel"));
std::env::set_var("KEYFORKD_SOCKET_PATH", &socket_path);
let test_handle = tokio::task::spawn_blocking(move || closure(&socket_path)); let test_handle = tokio::task::spawn_blocking(move || closure(&socket_path));
let result = test_handle.await; let result = test_handle.await;
@ -89,8 +105,8 @@ where
result result
}); });
if let Err(e) = result { if let Err(e) = result {
if e.is_panic() { if let Ok(reason) = e.try_into_panic() {
std::panic::resume_unwind(e.into_panic()); std::panic::resume_unwind(reason);
} }
} }
Ok(()) Ok(())
@ -103,6 +119,6 @@ mod tests {
#[test] #[test]
fn test_run_test() { fn test_run_test() {
let seed = b"beefbeef"; let seed = b"beefbeef";
run_test(seed, |_path| Infallible::Ok(())).expect("infallible"); run_test(seed, |_path| Panicable::Ok(())).expect("infallible");
} }
} }

View File

@ -16,3 +16,4 @@ ed25519-dalek = "2.0.0"
sequoia-openpgp = { version = "1.17.0", default-features = false } sequoia-openpgp = { version = "1.17.0", default-features = false }
anyhow = "1.0.75" anyhow = "1.0.75"
thiserror = "1.0.49" thiserror = "1.0.49"
keyfork-derive-path-data = { version = "0.1.1", path = "../keyfork-derive-path-data" }

View File

@ -2,7 +2,8 @@
use std::{env, process::ExitCode, str::FromStr}; use std::{env, process::ExitCode, str::FromStr};
use keyfork_derive_util::{DerivationIndex, DerivationPath}; use keyfork_derive_util::DerivationPath;
use keyfork_derive_path_data::paths;
use keyforkd_client::Client; use keyforkd_client::Client;
use ed25519_dalek::SigningKey; use ed25519_dalek::SigningKey;
@ -78,16 +79,14 @@ fn validate(
subkey_format: &str, subkey_format: &str,
default_userid: &str, default_userid: &str,
) -> Result<(DerivationPath, Vec<KeyType>, UserID), Box<dyn std::error::Error>> { ) -> Result<(DerivationPath, Vec<KeyType>, UserID), Box<dyn std::error::Error>> {
let mut pgp_u32 = [0u8; 4]; let index = paths::OPENPGP.inner().first().unwrap();
pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::<Vec<u8>>());
let index = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?;
let path = DerivationPath::from_str(path)?; let path = DerivationPath::from_str(path)?;
assert!(path.len() >= 2, "Expected path of at least m/{index}/account_id'"); assert!(path.len() >= 2, "Expected path of at least m/{index}/account_id'");
let given_index = path.iter().next().expect("checked .len() above"); let given_index = path.iter().next().expect("checked .len() above");
assert_eq!( assert_eq!(
&index, given_index, index, given_index,
"Expected derivation path starting with m/{index}, got: {given_index}", "Expected derivation path starting with m/{index}, got: {given_index}",
); );
@ -122,7 +121,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?; let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;
for packet in cert.into_packets() { for packet in cert.into_packets2() {
packet.serialize(&mut w)?; packet.serialize(&mut w)?;
} }

View File

@ -8,3 +8,4 @@ license = "MIT"
[dependencies] [dependencies]
keyfork-derive-util = { version = "0.2.0", path = "../keyfork-derive-util", default-features = false, registry = "distrust" } keyfork-derive-util = { version = "0.2.0", path = "../keyfork-derive-util", default-features = false, registry = "distrust" }
once_cell = "1.19.0"

View File

@ -2,32 +2,128 @@
#![allow(clippy::unreadable_literal)] #![allow(clippy::unreadable_literal)]
use once_cell::sync::Lazy;
use keyfork_derive_util::{DerivationIndex, DerivationPath}; use keyfork_derive_util::{DerivationIndex, DerivationPath};
pub mod paths {
use super::*;
/// The default derivation path for OpenPGP. /// The default derivation path for OpenPGP.
pub static OPENPGP: DerivationIndex = DerivationIndex::new_unchecked(7366512, true); pub static OPENPGP: Lazy<DerivationPath> = Lazy::new(|| {
DerivationPath::default().chain_push(DerivationIndex::new_unchecked(
u32::from_be_bytes(*b"\x00pgp"),
true,
))
});
/// The derivation path for OpenPGP certificates used for sharding.
pub static OPENPGP_SHARD: Lazy<DerivationPath> = Lazy::new(|| {
DerivationPath::default()
.chain_push(DerivationIndex::new_unchecked(
u32::from_be_bytes(*b"\x00pgp"),
true,
))
.chain_push(DerivationIndex::new_unchecked(
u32::from_be_bytes(*b"shrd"),
true,
))
});
/// The derivation path for OpenPGP certificates used for disaster recovery.
pub static OPENPGP_DISASTER_RECOVERY: Lazy<DerivationPath> = Lazy::new(|| {
DerivationPath::default()
.chain_push(DerivationIndex::new_unchecked(
u32::from_be_bytes(*b"\x00pgp"),
true,
))
.chain_push(DerivationIndex::new_unchecked(
u32::from_be_bytes(*b"\x00\x00dr"),
true,
))
});
}
/// Determine if a prefix matches and whether the next index exists.
fn prefix_matches(given: &DerivationPath, target: &DerivationPath) -> Option<DerivationIndex> {
if given.len() <= target.len() {
return None;
}
if target
.iter()
.zip(given.iter())
.all(|(left, right)| left == right)
{
given.iter().nth(target.len()).cloned()
} else {
None
}
}
/// A derivation target. /// A derivation target.
#[derive(Debug)]
#[non_exhaustive]
pub enum Target { pub enum Target {
/// An OpenPGP key, whose account is the given index. /// An OpenPGP key, whose account is the given index.
OpenPGP(DerivationIndex), OpenPGP(DerivationIndex),
/// An OpenPGP key used for sharding.
OpenPGPShard(DerivationIndex),
/// An OpenPGP key used for disaster recovery.
OpenPGPDisasterRecovery(DerivationIndex),
} }
impl std::fmt::Display for Target { impl std::fmt::Display for Target {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::OpenPGP(account) => { Target::OpenPGP(account) => {
write!(f, "OpenPGP key (account {account})") write!(f, "OpenPGP key (account {account})")
} }
Target::OpenPGPShard(shard_index) => {
write!(f, "OpenPGP Shard key (shard index {shard_index})")
}
Target::OpenPGPDisasterRecovery(account) => {
write!(f, "OpenPGP Disaster Recovery key (account {account})")
}
} }
} }
} }
macro_rules! test_match {
($var:ident, $shard:path, $target:path) => {
if let Some(index) = prefix_matches($var, &$shard) {
return Some($target(index));
}
};
}
/// Determine the closest [`Target`] for the given path. This method is intended to be used by /// Determine the closest [`Target`] for the given path. This method is intended to be used by
/// `keyforkd` to provide an optional textual prompt to what a client is attempting to derive. /// `keyforkd` to provide an optional textual prompt to what a client is attempting to derive.
pub fn guess_target(path: &DerivationPath) -> Option<Target> { pub fn guess_target(path: &DerivationPath) -> Option<Target> {
Some(match path.iter().collect::<Vec<_>>()[..] { test_match!(path, paths::OPENPGP_SHARD, Target::OpenPGPShard);
[t, index] if t == &OPENPGP => Target::OpenPGP(index.clone()), test_match!(
_ => return None, path,
}) paths::OPENPGP_DISASTER_RECOVERY,
Target::OpenPGPDisasterRecovery
);
test_match!(path, paths::OPENPGP, Target::OpenPGP);
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let index = DerivationIndex::new(5312, false).unwrap();
let dr_key = paths::OPENPGP_DISASTER_RECOVERY
.clone()
.chain_push(index.clone());
match guess_target(&dr_key) {
Some(Target::OpenPGPDisasterRecovery(idx)) if idx == index => (),
bad => panic!("invalid value: {bad:?}"),
}
}
} }

View File

@ -12,7 +12,7 @@ secp256k1 = ["k256"]
ed25519 = ["ed25519-dalek"] ed25519 = ["ed25519-dalek"]
[dependencies] [dependencies]
keyfork-mnemonic-util = { version = "0.3.0", path = "../../util/keyfork-mnemonic-util", registry = "distrust" } keyfork-mnemonic = { version = "0.3.0", path = "../../util/keyfork-mnemonic", registry = "distrust" }
keyfork-bug = { version = "0.1.0", path = "../../util/keyfork-bug", registry = "distrust" } keyfork-bug = { version = "0.1.0", path = "../../util/keyfork-bug", registry = "distrust" }
# Included in Rust # Included in Rust

View File

@ -23,7 +23,7 @@ performed directly on a master seed. This is how Keyforkd works internally.
```rust ```rust
use std::str::FromStr; use std::str::FromStr;
use keyfork_mnemonic_util::Mnemonic; use keyfork_mnemonic::Mnemonic;
use keyfork_derive_util::{*, request::*}; use keyfork_derive_util::{*, request::*};
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {

View File

@ -11,7 +11,7 @@
//! # Examples //! # Examples
//! ```rust //! ```rust
//! use std::str::FromStr; //! use std::str::FromStr;
//! use keyfork_mnemonic_util::Mnemonic; //! use keyfork_mnemonic::Mnemonic;
//! use keyfork_derive_util::{*, request::*}; //! use keyfork_derive_util::{*, request::*};
//! use k256::SecretKey; //! use k256::SecretKey;
//! //!

View File

@ -24,7 +24,7 @@ use crate::{
DerivationPath, ExtendedPrivateKey, DerivationPath, ExtendedPrivateKey,
}; };
use keyfork_mnemonic_util::{Mnemonic, MnemonicGenerationError}; use keyfork_mnemonic::{Mnemonic, MnemonicGenerationError};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// An error encountered while deriving a key. /// An error encountered while deriving a key.
@ -194,8 +194,8 @@ impl DerivationRequest {
/// # private_key::TestPrivateKey as PrivateKey, /// # private_key::TestPrivateKey as PrivateKey,
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mnemonic: keyfork_mnemonic_util::Mnemonic = // /// let mnemonic: keyfork_mnemonic::Mnemonic = //
/// # keyfork_mnemonic_util::Mnemonic::from_entropy( /// # keyfork_mnemonic::Mnemonic::from_entropy(
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
/// # )?; /// # )?;
/// let algo: DerivationAlgorithm = // /// let algo: DerivationAlgorithm = //

View File

@ -23,7 +23,7 @@ sharks = "0.5.0"
thiserror = "1.0.50" thiserror = "1.0.50"
# Remote operator mode # Remote operator mode
keyfork-mnemonic-util = { version = "0.3.0", path = "../util/keyfork-mnemonic-util", registry = "distrust" } keyfork-mnemonic = { version = "0.3.0", path = "../util/keyfork-mnemonic", registry = "distrust" }
x25519-dalek = { version = "2.0.0", features = ["getrandom"] } x25519-dalek = { version = "2.0.0", features = ["getrandom"] }
aes-gcm = { version = "0.10.3", features = ["std"] } aes-gcm = { version = "0.10.3", features = ["std"] }
hkdf = { version = "0.12.4", features = ["std"] } hkdf = { version = "0.12.4", features = ["std"] }

View File

@ -13,7 +13,7 @@ use aes_gcm::{
use base64::prelude::{Engine, BASE64_STANDARD}; use base64::prelude::{Engine, BASE64_STANDARD};
use hkdf::Hkdf; use hkdf::Hkdf;
use keyfork_bug::{bug, POISONED_MUTEX}; use keyfork_bug::{bug, POISONED_MUTEX};
use keyfork_mnemonic_util::{English, Mnemonic}; use keyfork_mnemonic::{English, Mnemonic};
use keyfork_prompt::{ use keyfork_prompt::{
validators::{ validators::{
mnemonic::{MnemonicSetValidator, MnemonicValidator, WordLength}, mnemonic::{MnemonicSetValidator, MnemonicValidator, WordLength},
@ -249,7 +249,7 @@ pub trait Format {
// create our shared key // create our shared key
let our_key = EphemeralSecret::random(); let our_key = EphemeralSecret::random();
let our_pubkey_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?; let our_pubkey_mnemonic = Mnemonic::try_from_slice(PublicKey::from(&our_key).as_bytes())?;
let shared_secret = our_key.diffie_hellman(&PublicKey::from(their_pubkey)); let shared_secret = our_key.diffie_hellman(&PublicKey::from(their_pubkey));
assert!( assert!(
shared_secret.was_contributory(), shared_secret.was_contributory(),
@ -302,7 +302,7 @@ pub trait Format {
let mut mnemonic_bytes = [0u8; ENCRYPTED_LENGTH as usize]; let mut mnemonic_bytes = [0u8; ENCRYPTED_LENGTH as usize];
mnemonic_bytes.copy_from_slice(&encrypted_bytes); mnemonic_bytes.copy_from_slice(&encrypted_bytes);
let payload_mnemonic = Mnemonic::from_nonstandard_bytes(mnemonic_bytes); let payload_mnemonic = Mnemonic::from_array(mnemonic_bytes);
#[cfg(feature = "qrcode")] #[cfg(feature = "qrcode")]
{ {
@ -439,7 +439,7 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
while iter_count.is_none() || iter_count.is_some_and(|i| i > 0) { while iter_count.is_none() || iter_count.is_some_and(|i| i > 0) {
iter += 1; iter += 1;
let our_key = EphemeralSecret::random(); let our_key = EphemeralSecret::random();
let key_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?; let key_mnemonic = Mnemonic::try_from_slice(PublicKey::from(&our_key).as_bytes())?;
#[cfg(feature = "qrcode")] #[cfg(feature = "qrcode")]
{ {

View File

@ -29,7 +29,7 @@ keyforkd-client = { version = "0.2.0", path = "../daemon/keyforkd-client", defau
keyfork-derive-openpgp = { version = "0.1.1", path = "../derive/keyfork-derive-openpgp", registry = "distrust" } keyfork-derive-openpgp = { version = "0.1.1", path = "../derive/keyfork-derive-openpgp", registry = "distrust" }
keyfork-derive-util = { version = "0.2.0", path = "../derive/keyfork-derive-util", default-features = false, features = ["ed25519"], registry = "distrust" } keyfork-derive-util = { version = "0.2.0", path = "../derive/keyfork-derive-util", default-features = false, features = ["ed25519"], registry = "distrust" }
keyfork-entropy = { version = "0.1.0", path = "../util/keyfork-entropy", registry = "distrust" } keyfork-entropy = { version = "0.1.0", path = "../util/keyfork-entropy", registry = "distrust" }
keyfork-mnemonic-util = { version = "0.3.0", path = "../util/keyfork-mnemonic-util", registry = "distrust" } keyfork-mnemonic = { version = "0.3.0", path = "../util/keyfork-mnemonic", registry = "distrust" }
keyfork-prompt = { version = "0.1.0", path = "../util/keyfork-prompt", registry = "distrust" } keyfork-prompt = { version = "0.1.0", path = "../util/keyfork-prompt", registry = "distrust" }
keyfork-qrcode = { version = "0.1.0", path = "../qrcode/keyfork-qrcode", default-features = false, registry = "distrust" } keyfork-qrcode = { version = "0.1.0", path = "../qrcode/keyfork-qrcode", default-features = false, registry = "distrust" }
keyfork-shard = { version = "0.2.0", path = "../keyfork-shard", default-features = false, features = ["openpgp", "openpgp-card", "qrcode"], registry = "distrust" } keyfork-shard = { version = "0.2.0", path = "../keyfork-shard", default-features = false, features = ["openpgp", "openpgp-card", "qrcode"], registry = "distrust" }
@ -44,3 +44,4 @@ openpgp-card-sequoia = { version = "0.2.0", default-features = false }
openpgp-card = "0.4.1" openpgp-card = "0.4.1"
clap_complete = { version = "4.4.6", optional = true } clap_complete = { version = "4.4.6", optional = true }
sequoia-openpgp = { version = "1.17.0", default-features = false, features = ["compression"] } sequoia-openpgp = { version = "1.17.0", default-features = false, features = ["compression"] }
keyfork-derive-path-data = { version = "0.1.1", path = "../derive/keyfork-derive-path-data" }

View File

@ -1,5 +1,5 @@
use super::Keyfork; use super::Keyfork;
use clap::{Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use keyfork_derive_openpgp::{ use keyfork_derive_openpgp::{
openpgp::{ openpgp::{
@ -10,7 +10,8 @@ use keyfork_derive_openpgp::{
}, },
XPrvKey, XPrvKey,
}; };
use keyfork_derive_util::{DerivationIndex, DerivationPath}; use keyfork_derive_util::DerivationIndex;
use keyfork_derive_path_data::paths;
use keyforkd_client::Client; use keyforkd_client::Client;
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>; type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
@ -27,22 +28,26 @@ pub enum DeriveSubcommands {
/// It is recommended to use the default expiration of one day and to change the expiration /// It is recommended to use the default expiration of one day and to change the expiration
/// using an external utility, to ensure the Certify key is usable. /// using an external utility, to ensure the Certify key is usable.
#[command(name = "openpgp")] #[command(name = "openpgp")]
OpenPGP { OpenPGP(OpenPGP)
}
#[derive(Args, Clone, Debug)]
pub struct OpenPGP {
/// Default User ID for the certificate, using the OpenPGP User ID format. /// Default User ID for the certificate, using the OpenPGP User ID format.
user_id: String, user_id: String,
},
} }
impl DeriveSubcommands { impl DeriveSubcommands {
fn handle(&self, account: DerivationIndex) -> Result<()> { fn handle(&self, account: DerivationIndex) -> Result<()> {
match self { match self {
DeriveSubcommands::OpenPGP { user_id } => { DeriveSubcommands::OpenPGP(opgp) => opgp.handle(account),
let mut pgp_u32 = [0u8; 4]; }
pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::<Vec<u8>>()); }
let chain = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?; }
let path = DerivationPath::default()
.chain_push(chain) impl OpenPGP {
.chain_push(account); pub fn handle(&self, account: DerivationIndex) -> Result<()> {
let path = paths::OPENPGP.clone().chain_push(account);
// TODO: should this be customizable? // TODO: should this be customizable?
let subkeys = vec![ let subkeys = vec![
KeyFlags::empty().set_certification(), KeyFlags::empty().set_certification(),
@ -53,19 +58,16 @@ impl DeriveSubcommands {
KeyFlags::empty().set_authentication(), KeyFlags::empty().set_authentication(),
]; ];
let xprv = Client::discover_socket()?.request_xprv::<XPrvKey>(&path)?; let xprv = Client::discover_socket()?.request_xprv::<XPrvKey>(&path)?;
let default_userid = UserID::from(user_id.as_str()); let default_userid = UserID::from(self.user_id.as_str());
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &default_userid)?; let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &default_userid)?;
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?; let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;
for packet in cert.into_packets() { for packet in cert.into_packets2() {
packet.serialize(&mut w)?; packet.serialize(&mut w)?;
} }
w.finalize()?; w.finalize()?;
}
}
Ok(()) Ok(())
} }
} }

View File

@ -109,7 +109,7 @@ impl MnemonicSeedSource {
MnemonicSeedSource::Tarot => todo!(), MnemonicSeedSource::Tarot => todo!(),
MnemonicSeedSource::Dice => todo!(), MnemonicSeedSource::Dice => todo!(),
}; };
let mnemonic = keyfork_mnemonic_util::Mnemonic::from_bytes(&seed)?; let mnemonic = keyfork_mnemonic::Mnemonic::try_from_slice(&seed)?;
Ok(mnemonic.to_string()) Ok(mnemonic.to_string())
} }
} }

View File

@ -2,7 +2,7 @@ use super::Keyfork;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use std::path::PathBuf; use std::path::PathBuf;
use keyfork_mnemonic_util::{English, Mnemonic}; use keyfork_mnemonic::{English, Mnemonic};
use keyfork_prompt::{default_terminal, DefaultTerminal}; use keyfork_prompt::{default_terminal, DefaultTerminal};
use keyfork_shard::{remote_decrypt, Format}; use keyfork_shard::{remote_decrypt, Format};
@ -85,7 +85,7 @@ pub struct Recover {
impl Recover { impl Recover {
pub fn handle(&self, _k: &Keyfork) -> Result<()> { pub fn handle(&self, _k: &Keyfork) -> Result<()> {
let seed = self.command.handle()?; let seed = self.command.handle()?;
let mnemonic = Mnemonic::from_bytes(&seed)?; let mnemonic = Mnemonic::try_from_slice(&seed)?;
tokio::runtime::Builder::new_multi_thread() tokio::runtime::Builder::new_multi_thread()
.enable_all() .enable_all()
.build() .build()

View File

@ -1,26 +1,29 @@
use super::Keyfork; use super::Keyfork;
use clap::{Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use std::{ use std::{collections::HashSet, fs::File, io::IsTerminal, path::PathBuf};
collections::HashSet,
fs::File,
io::IsTerminal,
path::{Path, PathBuf},
};
use card_backend_pcsc::PcscBackend; use card_backend_pcsc::PcscBackend;
use openpgp_card_sequoia::{state::Open, types::KeyType, Card}; use openpgp_card_sequoia::{state::Open, types::KeyType, Card};
use keyfork_derive_openpgp::{ use keyfork_derive_openpgp::{
openpgp::{self, packet::UserID, types::KeyFlags, Cert, serialize::Marshal, armor::{Writer, Kind}}, openpgp::{
self,
armor::{Kind, Writer},
packet::UserID,
serialize::Marshal,
types::KeyFlags,
Cert,
},
XPrv, XPrv,
}; };
use keyfork_derive_util::{DerivationIndex, DerivationPath, VariableLengthSeed}; use keyfork_derive_path_data::paths;
use keyfork_derive_util::DerivationIndex;
use keyfork_mnemonic::Mnemonic;
use keyfork_prompt::{ use keyfork_prompt::{
default_terminal, default_terminal,
validators::{SecurePinValidator, Validator}, validators::{SecurePinValidator, Validator},
DefaultTerminal, Message, PromptHandler, DefaultTerminal, Message, PromptHandler,
}; };
use keyfork_mnemonic_util::Mnemonic;
use keyfork_shard::{openpgp::OpenPGP, Format}; use keyfork_shard::{openpgp::OpenPGP, Format};
@ -42,17 +45,8 @@ fn derive_key(seed: [u8; 32], index: u8) -> Result<Cert> {
KeyFlags::empty().set_authentication(), KeyFlags::empty().set_authentication(),
]; ];
let mut pgp_u32 = [0u8; 4];
pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::<Vec<u8>>());
let chain = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?;
let mut shrd_u32 = [0u8; 4];
shrd_u32[..].copy_from_slice(&"shrd".bytes().collect::<Vec<u8>>());
let account = DerivationIndex::new(u32::from_be_bytes(shrd_u32), true)?;
let subkey = DerivationIndex::new(u32::from(index), true)?; let subkey = DerivationIndex::new(u32::from(index), true)?;
let path = DerivationPath::default() let path = paths::OPENPGP_SHARD.clone().chain_push(subkey);
.chain_push(chain)
.chain_push(account)
.chain_push(subkey);
let xprv = XPrv::new(seed) let xprv = XPrv::new(seed)
.expect("could not construct master key from seed") .expect("could not construct master key from seed")
.derive_path(&path)?; .derive_path(&path)?;
@ -109,18 +103,82 @@ fn factory_reset_current_card(
Ok(()) Ok(())
} }
fn generate_shard_secret( #[derive(Subcommand, Clone, Debug)]
pub enum WizardSubcommands {
GenerateShardSecret(GenerateShardSecret),
BottomsUp(BottomsUp),
}
/// Create a 256 bit secret and shard the secret to smart cards.
///
/// Smart cards will need to be plugged in periodically during the wizard, where they will be
/// factory reset and provisioned to `m/pgp'/shrd'/<share index>`. The secret can then be recovered
/// with `keyfork recover shard` or `keyfork recover remote-shard`. The share file will be printed
/// to standard output.
#[derive(Args, Clone, Debug)]
pub struct GenerateShardSecret {
/// The minimum amount of keys required to decrypt the secret.
#[arg(long)]
threshold: u8, threshold: u8,
/// The maximum amount of shards.
#[arg(long)]
max: u8, max: u8,
/// The amount of smart cards to provision per-shard.
#[arg(long, default_value = "1")]
keys_per_shard: u8, keys_per_shard: u8,
output_file: &Option<PathBuf>,
) -> Result<()> { /// The file to write the generated shard file to.
#[arg(long)]
output: Option<PathBuf>,
}
/// Create a 256 bit secret and shard the secret to previously known OpenPGP certificates,
/// deriving the default OpenPGP certificate for the secret.
///
/// This command was purpose-built for DEFCON and is not intended to be used normally, as it
/// implies keys used for sharding have been generated by a custom source.
#[derive(Args, Clone, Debug)]
pub struct BottomsUp {
/// The location of OpenPGP certificates to use when sharding.
key_discovery: PathBuf,
/// The minimum amount of keys required to decrypt the secret.
#[arg(long)]
threshold: u8,
/// The file to write the generated shard file to.
#[arg(long)]
output_shardfile: PathBuf,
/// The file to write the generated OpenPGP certificate to.
#[arg(long)]
output_cert: PathBuf,
/// The User ID for the generated OpenPGP certificate.
#[arg(long, default_value = "Disaster Recovery")]
user_id: String,
}
impl WizardSubcommands {
// dispatch
fn handle(&self) -> Result<()> {
match self {
WizardSubcommands::GenerateShardSecret(gss) => gss.handle(),
WizardSubcommands::BottomsUp(bu) => bu.handle(),
}
}
}
impl GenerateShardSecret {
fn handle(&self) -> Result<()> {
let seed = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?; let seed = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?;
let mut pm = default_terminal()?; let mut pm = default_terminal()?;
let mut certs = vec![]; let mut certs = vec![];
let mut seen_cards: HashSet<String> = HashSet::new(); let mut seen_cards: HashSet<String> = HashSet::new();
let stdout = std::io::stdout(); let stdout = std::io::stdout();
if output_file.is_none() { if self.output.is_none() {
assert!( assert!(
!stdout.is_terminal(), !stdout.is_terminal(),
"not printing shard to terminal, redirect output" "not printing shard to terminal, redirect output"
@ -138,9 +196,9 @@ fn generate_shard_secret(
} }
.to_fn(); .to_fn();
for index in 0..max { for index in 0..self.max {
let cert = derive_key(seed, index)?; let cert = derive_key(seed, index)?;
for i in 0..keys_per_shard { for i in 0..self.keys_per_shard {
pm.prompt_message(Message::Text(format!( pm.prompt_message(Message::Text(format!(
"Please remove all keys and insert key #{} for user #{}", "Please remove all keys and insert key #{} for user #{}",
(i as u16) + 1, (i as u16) + 1,
@ -178,12 +236,12 @@ fn generate_shard_secret(
let opgp = OpenPGP::<DefaultTerminal>::new(); let opgp = OpenPGP::<DefaultTerminal>::new();
if let Some(output_file) = output_file { if let Some(output_file) = self.output.as_ref() {
let output = File::create(output_file)?; let output = File::create(output_file)?;
opgp.shard_and_encrypt(threshold, certs.len() as u8, &seed, &certs[..], output)?; opgp.shard_and_encrypt(self.threshold, certs.len() as u8, &seed, &certs[..], output)?;
} else { } else {
opgp.shard_and_encrypt( opgp.shard_and_encrypt(
threshold, self.threshold,
certs.len() as u8, certs.len() as u8,
&seed, &seed,
&certs[..], &certs[..],
@ -192,18 +250,18 @@ fn generate_shard_secret(
} }
Ok(()) Ok(())
} }
}
fn bottoms_up(key_discovery: &Path, threshold: u8, output_shardfile: &Path, output_cert: &Path, user_id: &str,) -> Result<()> { impl BottomsUp {
fn handle(&self) -> Result<()> {
let entropy = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?; let entropy = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?;
let mnemonic = Mnemonic::from_nonstandard_bytes(entropy); let mnemonic = Mnemonic::from_array(entropy);
// TODO: make this return const size, since is hash based
let seed = mnemonic.generate_seed(None); let seed = mnemonic.generate_seed(None);
// TODO: should this allow for customizing the account index from 0? Potential for key reuse // TODO: should this allow for customizing the account index from 0? Potential for key reuse
// errors. // errors.
let path = DerivationPath::default() let path = paths::OPENPGP_DISASTER_RECOVERY
.chain_push(DerivationIndex::new(u32::from_be_bytes(*b"\x00pgp"), true)?) .clone()
.chain_push(DerivationIndex::new(u32::from_be_bytes(*b"\x00\x00dr"), true)?)
.chain_push(DerivationIndex::new(0, true)?); .chain_push(DerivationIndex::new(0, true)?);
let subkeys = [ let subkeys = [
KeyFlags::empty().set_certification(), KeyFlags::empty().set_certification(),
@ -213,96 +271,30 @@ fn bottoms_up(key_discovery: &Path, threshold: u8, output_shardfile: &Path, outp
.set_storage_encryption(), .set_storage_encryption(),
KeyFlags::empty().set_authentication(), KeyFlags::empty().set_authentication(),
]; ];
let xprv = XPrv::new(VariableLengthSeed::new(&seed)) let xprv = XPrv::new(seed)
.expect("could not construct master key from seed") .expect("could not construct master key from seed")
.derive_path(&path)?; .derive_path(&path)?;
let userid = UserID::from(user_id); let userid = UserID::from(self.user_id.as_str());
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &userid)?; let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &userid)?;
let certfile = File::create(output_cert)?; let certfile = File::create(&self.output_cert)?;
let mut w = Writer::new(certfile, Kind::PublicKey)?; let mut w = Writer::new(certfile, Kind::PublicKey)?;
cert.serialize(&mut w)?; cert.serialize(&mut w)?;
w.finalize()?; w.finalize()?;
let opgp = OpenPGP::<DefaultTerminal>::new(); let opgp = OpenPGP::<DefaultTerminal>::new();
let certs = OpenPGP::<DefaultTerminal>::discover_certs(key_discovery)?; let certs = OpenPGP::<DefaultTerminal>::discover_certs(&self.key_discovery)?;
let shardfile = File::create(output_shardfile)?;
opgp.shard_and_encrypt(threshold, certs.len() as u8, &entropy, &certs[..], shardfile)?;
let shardfile = File::create(&self.output_shardfile)?;
opgp.shard_and_encrypt(
self.threshold,
certs.len() as u8,
&entropy,
&certs[..],
shardfile,
)?;
Ok(()) Ok(())
} }
#[derive(Subcommand, Clone, Debug)]
pub enum WizardSubcommands {
/// Create a 256 bit secret and shard the secret to smart cards.
///
/// Smart cards will need to be plugged in periodically during the wizard, where they will be factory reset and
/// provisioned to `m/pgp'/shrd'/<share index>`. The secret can then be recovered with `keyfork recover shard` or
/// `keyfork recover remote-shard`. The share file will be printed to standard output.
GenerateShardSecret {
/// The minimum amount of keys required to decrypt the secret.
#[arg(long)]
threshold: u8,
/// The maximum amount of shards.
#[arg(long)]
max: u8,
/// The amount of smart cards to provision per-shard.
#[arg(long, default_value = "1")]
keys_per_shard: u8,
/// The file to write the generated shard file to.
#[arg(long)]
output: Option<PathBuf>,
},
/// Create a 256 bit secret and shard the secret to previously known OpenPGP certificates,
/// deriving the default OpenPGP certificate for the secret.
///
/// This command was purpose-built for DEFCON and is not intended to be used normally, as it
/// implies keys used for sharding have been generated by a custom source.
BottomsUp {
/// The location of OpenPGP certificates to use when sharding.
key_discovery: PathBuf,
/// The minimum amount of keys required to decrypt the secret.
#[arg(long)]
threshold: u8,
/// The file to write the generated shard file to.
#[arg(long)]
output_shardfile: PathBuf,
/// The file to write the generated OpenPGP certificate to.
#[arg(long)]
output_cert: PathBuf,
/// The User ID for the generated OpenPGP certificate.
#[arg(long, default_value = "Disaster Recovery")]
user_id: String,
},
}
impl WizardSubcommands {
fn handle(&self) -> Result<()> {
match self {
WizardSubcommands::GenerateShardSecret {
threshold,
max,
keys_per_shard,
output,
} => generate_shard_secret(*threshold, *max, *keys_per_shard, output),
WizardSubcommands::BottomsUp {
key_discovery,
threshold,
output_shardfile,
output_cert,
user_id,
} => bottoms_up(key_discovery, *threshold, output_shardfile, output_cert, user_id),
}
}
} }
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]

View File

@ -1,5 +1,5 @@
[package] [package]
name = "keyfork-mnemonic-util" name = "keyfork-mnemonic"
version = "0.3.0" version = "0.3.0"
description = "Utilities to generate and manage seeds based on BIP-0039 mnemonics." description = "Utilities to generate and manage seeds based on BIP-0039 mnemonics."
repository = "https://git.distrust.co/public/keyfork" repository = "https://git.distrust.co/public/keyfork"

View File

@ -1,6 +1,6 @@
//! //!
use keyfork_mnemonic_util::Mnemonic; use keyfork_mnemonic::Mnemonic;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = std::io::stdin(); let input = std::io::stdin();

View File

@ -3,17 +3,17 @@
//! Mnemonics can be used to safely encode data of 32, 48, and 64 bytes as a phrase: //! Mnemonics can be used to safely encode data of 32, 48, and 64 bytes as a phrase:
//! //!
//! ```rust //! ```rust
//! use keyfork_mnemonic_util::Mnemonic; //! use keyfork_mnemonic::Mnemonic;
//! let data = b"Hello, world! I am a mnemonic :)"; //! let data = b"Hello, world! I am a mnemonic :)";
//! assert_eq!(data.len(), 32); //! assert_eq!(data.len(), 32);
//! let mnemonic = Mnemonic::from_bytes(data).unwrap(); //! let mnemonic = Mnemonic::try_from_slice(data).unwrap();
//! println!("Our mnemonic is: {mnemonic}"); //! println!("Our mnemonic is: {mnemonic}");
//! ``` //! ```
//! //!
//! A mnemonic can also be parsed from a string: //! A mnemonic can also be parsed from a string:
//! //!
//! ```rust //! ```rust
//! use keyfork_mnemonic_util::Mnemonic; //! use keyfork_mnemonic::Mnemonic;
//! use std::str::FromStr; //! use std::str::FromStr;
//! //!
//! let data = b"Hello, world! I am a mnemonic :)"; //! let data = b"Hello, world! I am a mnemonic :)";
@ -28,7 +28,7 @@
//! verified to be safe: //! verified to be safe:
//! //!
//! ```rust //! ```rust
//! use keyfork_mnemonic_util::Mnemonic; //! use keyfork_mnemonic::Mnemonic;
//! let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; //! let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
//! let mnemonic = unsafe { Mnemonic::from_raw_bytes(data.as_slice()) }; //! let mnemonic = unsafe { Mnemonic::from_raw_bytes(data.as_slice()) };
//! let mnemonic_text = mnemonic.to_string(); //! let mnemonic_text = mnemonic.to_string();
@ -37,7 +37,7 @@
//! If given an invalid length, undefined behavior may follow, or code may panic. //! If given an invalid length, undefined behavior may follow, or code may panic.
//! //!
//! ```rust,should_panic //! ```rust,should_panic
//! use keyfork_mnemonic_util::Mnemonic; //! use keyfork_mnemonic::Mnemonic;
//! use std::str::FromStr; //! use std::str::FromStr;
//! //!
//! // NOTE: Data is of invalid length, 31 //! // NOTE: Data is of invalid length, 31
@ -268,11 +268,11 @@ where
/// ///
/// # Examples /// # Examples
/// ```rust /// ```rust
/// use keyfork_mnemonic_util::Mnemonic; /// use keyfork_mnemonic::Mnemonic;
/// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let mnemonic = Mnemonic::from_bytes(data.as_slice()).unwrap(); /// let mnemonic = Mnemonic::try_from_slice(data.as_slice()).unwrap();
/// ``` /// ```
pub fn from_bytes(bytes: &[u8]) -> Result<MnemonicBase<W>, MnemonicGenerationError> { pub fn try_from_slice(bytes: &[u8]) -> Result<MnemonicBase<W>, MnemonicGenerationError> {
let bit_count = bytes.len() * 8; let bit_count = bytes.len() * 8;
if bit_count % 32 != 0 { if bit_count % 32 != 0 {
@ -290,23 +290,23 @@ where
/// of a factor of 4, up to 1024 bytes. /// of a factor of 4, up to 1024 bytes.
/// ///
/// ```rust /// ```rust
/// use keyfork_mnemonic_util::Mnemonic; /// use keyfork_mnemonic::Mnemonic;
/// let data = b"hello world!"; /// let data = b"hello world!";
/// let mnemonic = Mnemonic::from_nonstandard_bytes(*data); /// let mnemonic = Mnemonic::from_array(*data);
/// ``` /// ```
/// ///
/// If an invalid size is requested, the code will fail to compile: /// If an invalid size is requested, the code will fail to compile:
/// ///
/// ```rust,compile_fail /// ```rust,compile_fail
/// use keyfork_mnemonic_util::Mnemonic; /// use keyfork_mnemonic::Mnemonic;
/// let mnemonic = Mnemonic::from_nonstandard_bytes([0u8; 53]); /// let mnemonic = Mnemonic::from_array([0u8; 53]);
/// ``` /// ```
/// ///
/// ```rust,compile_fail /// ```rust,compile_fail
/// use keyfork_mnemonic_util::Mnemonic; /// use keyfork_mnemonic::Mnemonic;
/// let mnemonic = Mnemonic::from_nonstandard_bytes([0u8; 1024 + 4]); /// let mnemonic = Mnemonic::from_array([0u8; 1024 + 4]);
/// ``` /// ```
pub fn from_nonstandard_bytes<const N: usize>(bytes: [u8; N]) -> MnemonicBase<W> { pub fn from_array<const N: usize>(bytes: [u8; N]) -> MnemonicBase<W> {
#[allow(clippy::let_unit_value)] #[allow(clippy::let_unit_value)]
{ {
let () = AssertValidMnemonicSize::<N>::OK_CHUNKS; let () = AssertValidMnemonicSize::<N>::OK_CHUNKS;
@ -315,16 +315,6 @@ where
Self::from_raw_bytes(&bytes) Self::from_raw_bytes(&bytes)
} }
/// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data is expected to be
/// of 128, 192, or 256 bits, as per BIP-0039.
///
/// # Errors
/// An error may be returned if the data is not within the expected lengths.
#[deprecated = "use Mnemonic::from_bytes"]
pub fn from_entropy(bytes: &[u8]) -> Result<MnemonicBase<W>, MnemonicGenerationError> {
MnemonicBase::from_bytes(bytes)
}
/// Create a Mnemonic using an arbitrary length of given data. The length does not need to /// Create a Mnemonic using an arbitrary length of given data. The length does not need to
/// conform to BIP-0039 standards, but should be a multiple of 32 bits or 4 bytes. /// conform to BIP-0039 standards, but should be a multiple of 32 bits or 4 bytes.
/// ///
@ -332,12 +322,12 @@ where
/// This function can potentially produce mnemonics that are not BIP-0039 compliant or can't /// This function can potentially produce mnemonics that are not BIP-0039 compliant or can't
/// properly be encoded as a mnemonic. It is assumed the caller asserts the byte count is `% 4 /// properly be encoded as a mnemonic. It is assumed the caller asserts the byte count is `% 4
/// == 0`. If the assumption is incorrect, code may panic. The /// == 0`. If the assumption is incorrect, code may panic. The
/// [`MnemonicBase::from_nonstandard_bytes`] function may be used to generate entropy if the /// [`MnemonicBase::from_array`] function may be used to generate entropy if the length of the
/// length of the data is known at compile-time. /// data is known at compile-time.
/// ///
/// # Examples /// # Examples
/// ```rust /// ```rust
/// use keyfork_mnemonic_util::Mnemonic; /// use keyfork_mnemonic::Mnemonic;
/// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let mnemonic = unsafe { Mnemonic::from_raw_bytes(data.as_slice()) }; /// let mnemonic = unsafe { Mnemonic::from_raw_bytes(data.as_slice()) };
/// let mnemonic_text = mnemonic.to_string(); /// let mnemonic_text = mnemonic.to_string();
@ -346,7 +336,7 @@ where
/// If given an invalid length, undefined behavior may follow, or code may panic. /// If given an invalid length, undefined behavior may follow, or code may panic.
/// ///
/// ```rust,should_panic /// ```rust,should_panic
/// use keyfork_mnemonic_util::Mnemonic; /// use keyfork_mnemonic::Mnemonic;
/// use std::str::FromStr; /// use std::str::FromStr;
/// ///
/// // NOTE: Data is of invalid length, 31 /// // NOTE: Data is of invalid length, 31
@ -383,16 +373,31 @@ where
&self.data &self.data
} }
/// A view to internal representation of the decoded data.
pub fn as_slice(&self) -> &[u8] {
&self.data
}
/// A clone of the internal representation of the decoded data. /// A clone of the internal representation of the decoded data.
pub fn to_bytes(&self) -> Vec<u8> { pub fn to_bytes(&self) -> Vec<u8> {
self.data.to_vec() self.data.to_vec()
} }
/// A clone of the internal representation of the decoded data.
pub fn to_vec(&self) -> Vec<u8> {
self.data.to_vec()
}
/// Conver the Mnemonic into the internal representation of the decoded data. /// Conver the Mnemonic into the internal representation of the decoded data.
pub fn into_bytes(self) -> Vec<u8> { pub fn into_bytes(self) -> Vec<u8> {
self.data self.data
} }
/// Conver the Mnemonic into the internal representation of the decoded data.
pub fn into_vec(self) -> Vec<u8> {
self.data
}
/// Clone the existing data. /// Clone the existing data.
#[deprecated = "Use as_bytes(), to_bytes(), or into_bytes() instead"] #[deprecated = "Use as_bytes(), to_bytes(), or into_bytes() instead"]
pub fn entropy(&self) -> Vec<u8> { pub fn entropy(&self) -> Vec<u8> {
@ -408,7 +413,7 @@ where
&self, &self,
passphrase: impl Into<Option<&'a str>>, passphrase: impl Into<Option<&'a str>>,
) -> Result<Vec<u8>, MnemonicGenerationError> { ) -> Result<Vec<u8>, MnemonicGenerationError> {
Ok(self.generate_seed(passphrase)) Ok(self.generate_seed(passphrase).to_vec())
} }
/// Create a BIP-0032 seed from the provided data and an optional passphrase. /// Create a BIP-0032 seed from the provided data and an optional passphrase.
@ -416,8 +421,7 @@ where
/// # Panics /// # Panics
/// The function may panic if the HmacSha512 function returns an error. The only error the /// The function may panic if the HmacSha512 function returns an error. The only error the
/// HmacSha512 function should return is an invalid length, which should not be possible. /// HmacSha512 function should return is an invalid length, which should not be possible.
/// pub fn generate_seed<'a>(&self, passphrase: impl Into<Option<&'a str>>) -> [u8; 64] {
pub fn generate_seed<'a>(&self, passphrase: impl Into<Option<&'a str>>) -> Vec<u8> {
let passphrase = passphrase.into(); let passphrase = passphrase.into();
let mut seed = [0u8; 64]; let mut seed = [0u8; 64];
@ -425,7 +429,7 @@ where
let salt = ["mnemonic", passphrase.unwrap_or("")].join(""); let salt = ["mnemonic", passphrase.unwrap_or("")].join("");
pbkdf2::<Hmac<Sha512>>(mnemonic.as_bytes(), salt.as_bytes(), 2048, &mut seed) pbkdf2::<Hmac<Sha512>>(mnemonic.as_bytes(), salt.as_bytes(), 2048, &mut seed)
.expect(bug!("HmacSha512 InvalidLength should be infallible")); .expect(bug!("HmacSha512 InvalidLength should be infallible"));
seed.to_vec() seed
} }
/// Encode the mnemonic into a list of integers 11 bits in length, matching the length of a /// Encode the mnemonic into a list of integers 11 bits in length, matching the length of a
@ -462,6 +466,39 @@ where
} }
} }
impl<W> MnemonicBase<W>
where
W: Wordlist,
{
/// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data is expected to be
/// of 128, 192, or 256 bits, as per BIP-0039.
///
/// # Errors
/// An error may be returned if the data is not within the expected lengths.
#[deprecated = "use Mnemonic::try_from_slice"]
pub fn from_bytes(bytes: &[u8]) -> Result<MnemonicBase<W>, MnemonicGenerationError> {
MnemonicBase::try_from_slice(bytes)
}
/// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data is expected to be
/// of 128, 192, or 256 bits, as per BIP-0039.
///
/// # Errors
/// An error may be returned if the data is not within the expected lengths.
#[deprecated = "use Mnemonic::try_from_slice"]
pub fn from_entropy(bytes: &[u8]) -> Result<MnemonicBase<W>, MnemonicGenerationError> {
MnemonicBase::try_from_slice(bytes)
}
/// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data may be of a size
/// of a factor of 4, up to 1024 bytes.
///
#[deprecated = "Use Mnemonic::from_array"]
pub fn from_nonstandard_bytes<const N: usize>(bytes: [u8; N]) -> MnemonicBase<W> {
MnemonicBase::from_array(bytes)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{collections::HashSet, fs::File, io::Read}; use std::{collections::HashSet, fs::File, io::Read};
@ -478,7 +515,7 @@ mod tests {
let mut random_handle = File::open("/dev/random").unwrap(); let mut random_handle = File::open("/dev/random").unwrap();
let entropy = &mut [0u8; 256 / 8]; let entropy = &mut [0u8; 256 / 8];
random_handle.read_exact(&mut entropy[..]).unwrap(); random_handle.read_exact(&mut entropy[..]).unwrap();
let mnemonic = super::Mnemonic::from_bytes(&entropy[..256 / 8]).unwrap(); let mnemonic = super::Mnemonic::try_from_slice(&entropy[..256 / 8]).unwrap();
let new_entropy = mnemonic.as_bytes(); let new_entropy = mnemonic.as_bytes();
assert_eq!(new_entropy, entropy); assert_eq!(new_entropy, entropy);
} }
@ -494,7 +531,7 @@ mod tests {
}; };
let hex = hex::decode(hex_.as_str().unwrap()).unwrap(); let hex = hex::decode(hex_.as_str().unwrap()).unwrap();
let mnemonic = Mnemonic::from_bytes(&hex).unwrap(); let mnemonic = Mnemonic::try_from_slice(&hex).unwrap();
assert_eq!(mnemonic.to_string(), seed.as_str().unwrap()); assert_eq!(mnemonic.to_string(), seed.as_str().unwrap());
} }
@ -505,7 +542,7 @@ mod tests {
let mut random_handle = File::open("/dev/random").unwrap(); let mut random_handle = File::open("/dev/random").unwrap();
let entropy = &mut [0u8; 256 / 8]; let entropy = &mut [0u8; 256 / 8];
random_handle.read_exact(&mut entropy[..]).unwrap(); random_handle.read_exact(&mut entropy[..]).unwrap();
let my_mnemonic = Mnemonic::from_bytes(&entropy[..256 / 8]).unwrap(); let my_mnemonic = Mnemonic::try_from_slice(&entropy[..256 / 8]).unwrap();
let their_mnemonic = bip39::Mnemonic::from_entropy(&entropy[..256 / 8]).unwrap(); let their_mnemonic = bip39::Mnemonic::from_entropy(&entropy[..256 / 8]).unwrap();
assert_eq!(my_mnemonic.to_string(), their_mnemonic.to_string()); assert_eq!(my_mnemonic.to_string(), their_mnemonic.to_string());
assert_eq!(my_mnemonic.generate_seed(None), their_mnemonic.to_seed("")); assert_eq!(my_mnemonic.generate_seed(None), their_mnemonic.to_seed(""));
@ -529,7 +566,7 @@ mod tests {
for _ in 0..tests { for _ in 0..tests {
random.read_exact(&mut entropy[..]).unwrap(); random.read_exact(&mut entropy[..]).unwrap();
let mnemonic = Mnemonic::from_bytes(&entropy[..256 / 8]).unwrap(); let mnemonic = Mnemonic::try_from_slice(&entropy[..256 / 8]).unwrap();
let words = mnemonic.words(); let words = mnemonic.words();
hs.clear(); hs.clear();
hs.extend(words); hs.extend(words);
@ -560,7 +597,7 @@ mod tests {
let mut entropy = [0u8; 1024]; let mut entropy = [0u8; 1024];
let mut random = std::fs::File::open("/dev/urandom").unwrap(); let mut random = std::fs::File::open("/dev/urandom").unwrap();
random.read_exact(&mut entropy[..]).unwrap(); random.read_exact(&mut entropy[..]).unwrap();
let mnemonic = Mnemonic::from_nonstandard_bytes(entropy); let mnemonic = Mnemonic::from_array(entropy);
let words = mnemonic.words(); let words = mnemonic.words();
assert_eq!(words.len(), 768); assert_eq!(words.len(), 768);
} }

View File

@ -10,10 +10,10 @@ license = "MIT"
[features] [features]
default = ["mnemonic"] default = ["mnemonic"]
mnemonic = ["keyfork-mnemonic-util"] mnemonic = ["keyfork-mnemonic"]
[dependencies] [dependencies]
keyfork-bug = { version = "0.1.0", path = "../keyfork-bug", registry = "distrust" } keyfork-bug = { version = "0.1.0", path = "../keyfork-bug", registry = "distrust" }
keyfork-crossterm = { version = "0.27.1", path = "../keyfork-crossterm", default-features = false, features = ["use-dev-tty", "events", "bracketed-paste"], registry = "distrust" } keyfork-crossterm = { version = "0.27.1", path = "../keyfork-crossterm", default-features = false, features = ["use-dev-tty", "events", "bracketed-paste"], registry = "distrust" }
keyfork-mnemonic-util = { version = "0.3.0", path = "../keyfork-mnemonic-util", optional = true, registry = "distrust" } keyfork-mnemonic = { version = "0.3.0", path = "../keyfork-mnemonic", optional = true, registry = "distrust" }
thiserror = "1.0.51" thiserror = "1.0.51"

View File

@ -7,7 +7,7 @@ use keyfork_prompt::{
Terminal, PromptHandler, Terminal, PromptHandler,
}; };
use keyfork_mnemonic_util::English; use keyfork_mnemonic::English;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut mgr = Terminal::new(stdin(), stdout())?; let mut mgr = Terminal::new(stdin(), stdout())?;

View File

@ -3,7 +3,7 @@
use std::borrow::Borrow; use std::borrow::Borrow;
#[cfg(feature = "mnemonic")] #[cfg(feature = "mnemonic")]
use keyfork_mnemonic_util::Wordlist; use keyfork_mnemonic::Wordlist;
/// ///
pub mod terminal; pub mod terminal;

View File

@ -158,7 +158,7 @@ pub mod mnemonic {
use super::Validator; use super::Validator;
use keyfork_bug::bug; use keyfork_bug::bug;
use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError}; use keyfork_mnemonic::{Mnemonic, MnemonicFromStrError};
/// A mnemonic could not be validated from the given input. /// A mnemonic could not be validated from the given input.
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]

View File

@ -113,6 +113,7 @@ allow = [
"Unicode-DFS-2016", "Unicode-DFS-2016",
"LGPL-2.0", "LGPL-2.0",
"LGPL-3.0", "LGPL-3.0",
"Unicode-3.0",
#"Apache-2.0 WITH LLVM-exception", #"Apache-2.0 WITH LLVM-exception",
] ]
# List of explicitly disallowed licenses # List of explicitly disallowed licenses

View File

@ -45,7 +45,7 @@ A command line interface for generating, deriving from, and managing secrets.
* [`keyfork-derive-openpgp`] * [`keyfork-derive-openpgp`]
* [`keyfork-derive-util`] * [`keyfork-derive-util`]
* [`keyfork-entropy`] * [`keyfork-entropy`]
* [`keyfork-mnemonic-util`] * [`keyfork-mnemonic`]
* [`keyfork-prompt`] * [`keyfork-prompt`]
* [`keyfork-qrcode`] * [`keyfork-qrcode`]
* [`keyfork-shard`] * [`keyfork-shard`]
@ -68,7 +68,7 @@ seed or close-to-root derivations.
* [`keyfork-derive-path-data`] * [`keyfork-derive-path-data`]
* [`keyfork-derive-util`] * [`keyfork-derive-util`]
* [`keyfork-frame`] * [`keyfork-frame`]
* [`keyfork-mnemonic-util`] * [`keyfork-mnemonic`]
* [`keyforkd-models`] * [`keyforkd-models`]
* [`serde`] * [`serde`]
* [`thiserror`] * [`thiserror`]
@ -129,7 +129,7 @@ BIP-0032 derivation.
* [`ed25519-dalek`]: Ed25519 key parsing and arithmetic. * [`ed25519-dalek`]: Ed25519 key parsing and arithmetic.
* [`hmac`]: Derivation of keys using HMAC. * [`hmac`]: Derivation of keys using HMAC.
* [`k256`]: secp256k1 (K-256) key parsing and arithmetic. * [`k256`]: secp256k1 (K-256) key parsing and arithmetic.
* [`keyfork-mnemonic-util`] * [`keyfork-mnemonic`]
* [`ripemd`]: Generating hash for fingerprinting of BIP-0032 derived data. * [`ripemd`]: Generating hash for fingerprinting of BIP-0032 derived data.
* [`serde`] * [`serde`]
* [`sha2`]: Generating hashes for fingerprinting and derivation of data. * [`sha2`]: Generating hashes for fingerprinting and derivation of data.
@ -145,7 +145,7 @@ M-of-N recombination of secret data using Shamir's Secret Sharing.
* [`card-backend-pcsc`]: PCSC support for OpenPGP-card. * [`card-backend-pcsc`]: PCSC support for OpenPGP-card.
* [`hkdf`]: Key derivation for transport encryption keys. * [`hkdf`]: Key derivation for transport encryption keys.
* [`keyfork-derive-openpgp`] * [`keyfork-derive-openpgp`]
* [`keyfork-mnemonic-util`]: Encoding encrypted shards using mnemonics. * [`keyfork-mnemonic`]: Encoding encrypted shards using mnemonics.
* [`keyfork-prompt`] * [`keyfork-prompt`]
* [`keyfork-qrcode`]: Encoding and decoding of encrypted shards using QR codes. * [`keyfork-qrcode`]: Encoding and decoding of encrypted shards using QR codes.
* [`openpgp-card`]: OpenPGP card support. * [`openpgp-card`]: OpenPGP card support.
@ -193,7 +193,7 @@ Frame data in a length-storing checksum-verified format.
* [`thiserror`] * [`thiserror`]
* [`tokio`]: Read and write from AsyncRead and AsyncWrite sources. * [`tokio`]: Read and write from AsyncRead and AsyncWrite sources.
## `keyfork-mnemonic-util` ## `keyfork-mnemonic`
* [`hmac`]: Hash utilities. * [`hmac`]: Hash utilities.
* [`sha2`]: Checksum of mnemonic data and hash for pbkdf2 * [`sha2`]: Checksum of mnemonic data and hash for pbkdf2
@ -202,7 +202,7 @@ Frame data in a length-storing checksum-verified format.
## `keyfork-prompt` ## `keyfork-prompt`
* [`keyfork-crossterm`]: Interacting with the terminal. * [`keyfork-crossterm`]: Interacting with the terminal.
* [`keyfork-mnemonic-util`] * [`keyfork-mnemonic`]
* [`thiserror`] * [`thiserror`]
## `keyfork-plumbing` ## `keyfork-plumbing`
@ -210,7 +210,7 @@ Frame data in a length-storing checksum-verified format.
Binaries for `keyfork-entropy` and `keyfork-mnemonic-from-seed`. Binaries for `keyfork-entropy` and `keyfork-mnemonic-from-seed`.
* [`keyfork-entropy`] * [`keyfork-entropy`]
* [`keyfork-mnemonic-util`] * [`keyfork-mnemonic`]
* [`smex`] * [`smex`]
## `keyfork-slip10-test-data` ## `keyfork-slip10-test-data`
@ -229,7 +229,7 @@ Zero-dependency hex encoding and decoding.
[`keyfork-derive-util`]: #keyfork-derive-util [`keyfork-derive-util`]: #keyfork-derive-util
[`keyfork-entropy`]: #keyfork-entropy [`keyfork-entropy`]: #keyfork-entropy
[`keyfork-frame`]: #keyfork-frame [`keyfork-frame`]: #keyfork-frame
[`keyfork-mnemonic-util`]: #keyfork-mnemonic-util [`keyfork-mnemonic`]: #keyfork-mnemonic
[`keyfork-prompt`]: #keyfork-prompt [`keyfork-prompt`]: #keyfork-prompt
[`keyfork-qrcode`]: #keyfork-qrcode [`keyfork-qrcode`]: #keyfork-qrcode
[`keyfork-shard`]: #keyfork-shard [`keyfork-shard`]: #keyfork-shard