diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2ea721b..d670178 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
+- Overhauled PEM auth. PEM files are now password-protected by default, and must be used instead of seed files. Passwords can be provided interactively or with `--password-file`. Keys can be generated unencrypted with `quill generate --storage-mode plaintext`, and encrypted keys can be converted to plaintext with `quill decrypt-pem`.
- Overhauled output format. All commands besides `quill sns` should have human-readable output instead of candid IDL. Candid IDL format can be forced with `--raw`.
## [0.4.4] - 2024-03-21
diff --git a/Cargo.lock b/Cargo.lock
index 120493c..21e2a9e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -23,6 +23,17 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
[[package]]
name = "ahash"
version = "0.7.8"
@@ -309,12 +320,6 @@ dependencies = [
"rustc-demangle",
]
-[[package]]
-name = "base16ct"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
-
[[package]]
name = "base16ct"
version = "0.2.0"
@@ -413,15 +418,15 @@ dependencies = [
[[package]]
name = "bip32"
-version = "0.4.0"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b30ed1d6f8437a487a266c8293aeb95b61a23261273e3e02912cdb8b68bf798b"
+checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164"
dependencies = [
- "bs58",
+ "bs58 0.5.1",
"hmac",
- "k256 0.11.6",
+ "k256",
"once_cell",
- "pbkdf2",
+ "pbkdf2 0.12.2",
"rand_core",
"ripemd",
"sha2 0.10.8",
@@ -486,6 +491,15 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "block-padding"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
+dependencies = [
+ "generic-array",
+]
+
[[package]]
name = "bls12_381"
version = "0.7.1"
@@ -529,8 +543,14 @@ name = "bs58"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
+
+[[package]]
+name = "bs58"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
dependencies = [
- "sha2 0.9.9",
+ "sha2 0.10.8",
]
[[package]]
@@ -758,6 +778,15 @@ dependencies = [
"serde_json",
]
+[[package]]
+name = "cbc"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
+dependencies = [
+ "cipher",
+]
+
[[package]]
name = "cc"
version = "1.0.98"
@@ -818,6 +847,16 @@ dependencies = [
"half 2.4.1",
]
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
[[package]]
name = "clap"
version = "3.2.25"
@@ -1032,18 +1071,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-[[package]]
-name = "crypto-bigint"
-version = "0.4.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef"
-dependencies = [
- "generic-array",
- "rand_core",
- "subtle",
- "zeroize",
-]
-
[[package]]
name = "crypto-bigint"
version = "0.5.5"
@@ -1231,17 +1258,6 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
-[[package]]
-name = "der"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
-dependencies = [
- "const-oid",
- "pem-rfc7468 0.6.0",
- "zeroize",
-]
-
[[package]]
name = "der"
version = "0.7.9"
@@ -1249,7 +1265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [
"const-oid",
- "pem-rfc7468 0.7.0",
+ "pem-rfc7468",
"zeroize",
]
@@ -1343,6 +1359,19 @@ dependencies = [
"prost",
]
+[[package]]
+name = "dialoguer"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
+dependencies = [
+ "console",
+ "shell-words",
+ "tempfile",
+ "thiserror",
+ "zeroize",
+]
+
[[package]]
name = "diff"
version = "0.1.13"
@@ -1426,30 +1455,18 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
-[[package]]
-name = "ecdsa"
-version = "0.14.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c"
-dependencies = [
- "der 0.6.1",
- "elliptic-curve 0.12.3",
- "rfc6979 0.3.1",
- "signature 1.6.4",
-]
-
[[package]]
name = "ecdsa"
version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
- "der 0.7.9",
+ "der",
"digest 0.10.7",
- "elliptic-curve 0.13.8",
- "rfc6979 0.4.0",
- "signature 2.2.0",
- "spki 0.7.3",
+ "elliptic-curve",
+ "rfc6979",
+ "signature",
+ "spki",
]
[[package]]
@@ -1458,8 +1475,8 @@ version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
- "pkcs8 0.10.2",
- "signature 2.2.0",
+ "pkcs8",
+ "signature",
]
[[package]]
@@ -1489,7 +1506,7 @@ dependencies = [
"rand_core",
"serde",
"sha2 0.10.8",
- "signature 2.2.0",
+ "signature",
"subtle",
"zeroize",
]
@@ -1500,43 +1517,22 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
-[[package]]
-name = "elliptic-curve"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3"
-dependencies = [
- "base16ct 0.1.1",
- "crypto-bigint 0.4.9",
- "der 0.6.1",
- "digest 0.10.7",
- "ff 0.12.1",
- "generic-array",
- "group 0.12.1",
- "pem-rfc7468 0.6.0",
- "pkcs8 0.9.0",
- "rand_core",
- "sec1 0.3.0",
- "subtle",
- "zeroize",
-]
-
[[package]]
name = "elliptic-curve"
version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
- "base16ct 0.2.0",
- "crypto-bigint 0.5.5",
+ "base16ct",
+ "crypto-bigint",
"digest 0.10.7",
"ff 0.13.0",
"generic-array",
"group 0.13.0",
- "pem-rfc7468 0.7.0",
- "pkcs8 0.10.2",
+ "pem-rfc7468",
+ "pkcs8",
"rand_core",
- "sec1 0.7.3",
+ "sec1",
"subtle",
"zeroize",
]
@@ -2125,17 +2121,17 @@ dependencies = [
"ic-certification",
"ic-transport-types",
"ic-verify-bls-signature",
- "k256 0.13.3",
+ "k256",
"leb128",
"p256",
"pem 2.0.1",
- "pkcs8 0.10.2",
+ "pkcs8",
"rand",
"rangemap",
"reqwest",
"ring",
"rustls-webpki",
- "sec1 0.7.3",
+ "sec1",
"serde",
"serde_bytes",
"serde_cbor",
@@ -2332,7 +2328,7 @@ source = "git+https://github.com/dfinity/ic?rev=479fc39a7ee082a62ec070efeed22478
dependencies = [
"async-trait",
"bech32",
- "bs58",
+ "bs58 0.4.0",
"candid",
"ciborium",
"hex",
@@ -2376,7 +2372,7 @@ name = "ic-crypto-ecdsa-secp256k1"
version = "0.9.0"
source = "git+https://github.com/dfinity/ic?rev=479fc39a7ee082a62ec070efeed224784a83eb1b#479fc39a7ee082a62ec070efeed224784a83eb1b"
dependencies = [
- "k256 0.13.3",
+ "k256",
"lazy_static",
"num-bigint 0.4.5",
"pem 1.1.1",
@@ -2555,7 +2551,7 @@ dependencies = [
"ic-crypto-secrets-containers",
"ic-crypto-sha2",
"ic-types",
- "k256 0.13.3",
+ "k256",
"lazy_static",
"p256",
"paste",
@@ -3736,6 +3732,16 @@ dependencies = [
"unicode-width",
]
+[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "block-padding",
+ "generic-array",
+]
+
[[package]]
name = "instant"
version = "0.1.13"
@@ -3829,19 +3835,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "k256"
-version = "0.11.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b"
-dependencies = [
- "cfg-if",
- "ecdsa 0.14.8",
- "elliptic-curve 0.12.3",
- "sha2 0.10.8",
- "sha3",
-]
-
[[package]]
name = "k256"
version = "0.13.3"
@@ -3849,11 +3842,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b"
dependencies = [
"cfg-if",
- "ecdsa 0.16.9",
- "elliptic-curve 0.13.8",
+ "ecdsa",
+ "elliptic-curve",
"once_cell",
"sha2 0.10.8",
- "signature 2.2.0",
+ "signature",
]
[[package]]
@@ -4406,8 +4399,8 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
dependencies = [
- "ecdsa 0.16.9",
- "elliptic-curve 0.13.8",
+ "ecdsa",
+ "elliptic-curve",
"primeorder",
"sha2 0.10.8",
]
@@ -4459,6 +4452,16 @@ dependencies = [
"digest 0.10.7",
]
+[[package]]
+name = "pbkdf2"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
+dependencies = [
+ "digest 0.10.7",
+ "hmac",
+]
+
[[package]]
name = "pem"
version = "1.1.1"
@@ -4478,15 +4481,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "pem-rfc7468"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac"
-dependencies = [
- "base64ct",
-]
-
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
@@ -4605,13 +4599,18 @@ dependencies = [
]
[[package]]
-name = "pkcs8"
-version = "0.9.0"
+name = "pkcs5"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba"
+checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6"
dependencies = [
- "der 0.6.1",
- "spki 0.6.0",
+ "aes",
+ "cbc",
+ "der",
+ "pbkdf2 0.12.2",
+ "scrypt",
+ "sha2 0.10.8",
+ "spki",
]
[[package]]
@@ -4620,8 +4619,10 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
- "der 0.7.9",
- "spki 0.7.3",
+ "der",
+ "pkcs5",
+ "rand_core",
+ "spki",
]
[[package]]
@@ -4727,7 +4728,7 @@ version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
dependencies = [
- "elliptic-curve 0.13.8",
+ "elliptic-curve",
]
[[package]]
@@ -4890,6 +4891,7 @@ dependencies = [
"clap 4.5.4",
"crc32fast",
"data-encoding",
+ "dialoguer",
"flate2",
"hex",
"hidapi",
@@ -4909,17 +4911,20 @@ dependencies = [
"icrc-ledger-types",
"indicatif",
"itertools 0.10.5",
- "k256 0.11.6",
+ "k256",
"ledger-apdu",
"ledger-canister",
"ledger-transport-hid",
"num-bigint 0.4.5",
"once_cell",
- "pem 1.1.1",
+ "pem 2.0.1",
+ "pkcs8",
"qrcodegen",
"rand",
+ "ring",
"rpassword",
"scopeguard",
+ "sec1",
"serde",
"serde_bytes",
"serde_cbor",
@@ -4928,7 +4933,6 @@ dependencies = [
"sha2 0.10.8",
"sha3",
"shellwords",
- "simple_asn1",
"tempfile",
"tiny-bip39",
"tokio",
@@ -5134,17 +5138,6 @@ dependencies = [
"winreg",
]
-[[package]]
-name = "rfc6979"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb"
-dependencies = [
- "crypto-bigint 0.4.9",
- "hmac",
- "zeroize",
-]
-
[[package]]
name = "rfc6979"
version = "0.4.0"
@@ -5338,6 +5331,15 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+[[package]]
+name = "salsa20"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
+dependencies = [
+ "cipher",
+]
+
[[package]]
name = "same-file"
version = "1.0.6"
@@ -5359,6 +5361,17 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+[[package]]
+name = "scrypt"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
+dependencies = [
+ "pbkdf2 0.12.2",
+ "salsa20",
+ "sha2 0.10.8",
+]
+
[[package]]
name = "sct"
version = "0.7.1"
@@ -5375,30 +5388,16 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
-[[package]]
-name = "sec1"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928"
-dependencies = [
- "base16ct 0.1.1",
- "der 0.6.1",
- "generic-array",
- "pkcs8 0.9.0",
- "subtle",
- "zeroize",
-]
-
[[package]]
name = "sec1"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
- "base16ct 0.2.0",
- "der 0.7.9",
+ "base16ct",
+ "der",
"generic-array",
- "pkcs8 0.10.2",
+ "pkcs8",
"subtle",
"zeroize",
]
@@ -5590,6 +5589,12 @@ dependencies = [
"keccak",
]
+[[package]]
+name = "shell-words"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
+
[[package]]
name = "shellwords"
version = "1.1.0"
@@ -5609,16 +5614,6 @@ dependencies = [
"libc",
]
-[[package]]
-name = "signature"
-version = "1.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
-dependencies = [
- "digest 0.10.7",
- "rand_core",
-]
-
[[package]]
name = "signature"
version = "2.2.0"
@@ -5730,16 +5725,6 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
-[[package]]
-name = "spki"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b"
-dependencies = [
- "base64ct",
- "der 0.6.1",
-]
-
[[package]]
name = "spki"
version = "0.7.3"
@@ -5747,7 +5732,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
- "der 0.7.9",
+ "der",
]
[[package]]
@@ -6017,7 +6002,7 @@ dependencies = [
"anyhow",
"hmac",
"once_cell",
- "pbkdf2",
+ "pbkdf2 0.11.0",
"rand",
"rustc-hash",
"sha2 0.10.8",
diff --git a/Cargo.toml b/Cargo.toml
index 4a395c2..e25bed4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -31,27 +31,30 @@ ic-identity-hsm = { git = "https://github.com/dfinity/agent-rs", rev = "8c39e262
anyhow = "1.0.34"
base64 = "0.13.0"
bigdecimal = "0.4"
-bip32 = "0.4.0"
+bip32 = "0.5.0"
chrono = "0.4"
clap = { version = "4.5.4", features = ["derive", "cargo", "color"] }
crc32fast = "1.3.2"
data-encoding = "2.3.3"
+dialoguer = "0.11.0"
flate2 = "1.0.22"
hex = { version = "0.4.2", features = ["serde"] }
hidapi = { version = "1.4", default-features = false, optional = true }
indicatif = "0.17"
itertools = "0.10.5"
-k256 = { version = "0.11.4", features = ["pem"] }
+k256 = { version = "0.13.0", features = ["pem", "pkcs8"] }
ledger-apdu = { version = "0.10", optional = true }
ledger-transport-hid = { version = "0.10", optional = true }
num-bigint = "0.4.3"
once_cell = "1.17.1"
-pem = "1.0.1"
+pem = "2.0.1"
+pkcs8 = { version = "0.10.0", features = ["encryption"] }
qrcodegen = "1.8"
rand = { version = "0.8.4", features = ["getrandom"] }
+ring = "0.17.7"
rpassword = "6.0.0"
-simple_asn1 = "0.6.1"
scopeguard = "1"
+sec1 = { version = "0.7.0", features = ["std"] }
serde = { version = "1.0.130", features = ["derive"] }
serde_bytes = "0.11.2"
serde_cbor = "0.11.2"
@@ -74,6 +77,12 @@ default = ["hsm", "ledger"]
[profile.release]
opt-level = 2
+[profile.dev.package.aes]
+opt-level = 2
+
+[profile.dev.package.scrypt]
+opt-level = 2
+
[package.metadata.binstall]
pkg-fmt = "bin"
bin-dir = ""
diff --git a/docs/cli-reference/index.mdx b/docs/cli-reference/index.mdx
index b6172aa..f965140 100644
--- a/docs/cli-reference/index.mdx
+++ b/docs/cli-reference/index.mdx
@@ -28,6 +28,7 @@ When you have quill installed, you can use the following commands to specify the
- [quill ckbtc transfer](./ckbtc/quill-ckbtc-transfer.mdx)
- [quill ckbtc update-balance](./ckbtc/quill-ckbtc-update-balance.mdx)
- [quill ckbtc withdrawal-address](./ckbtc/quill-ckbtc-withdrawal-address.mdx)
+- [quill decrypt-pem](./quill-decrypt-pem.mdx)
- [quill generate](./quill-generate.mdx)
- [quill get-neuron-info](./quill-get-neuron-info.mdx)
- [quill get-proposal-info](./quill-get-proposal-info.mdx)
diff --git a/docs/cli-reference/quill-decrypt-pem.mdx b/docs/cli-reference/quill-decrypt-pem.mdx
new file mode 100644
index 0000000..9b8fc67
--- /dev/null
+++ b/docs/cli-reference/quill-decrypt-pem.mdx
@@ -0,0 +1,48 @@
+import { MarkdownChipRow } from "/src/components/Chip/MarkdownChipRow";
+
+# quill decrypt-pem
+
+
+
+Decrypts an encrypted PEM file for use with other tools.
+
+## Basic usage
+
+The basic syntax for running `quill decrypt-pem` commands is:
+
+``` bash
+quill decrypt-pem [option]
+```
+
+## Arguments
+
+| Argument | Description |
+|-----------------|-----------------------------------------------------------|
+| `` | The path to write the decrypted PEM to, or "-" for STDOUT |
+
+## Flags
+
+| Flag | Description |
+|----------------|-----------------------------|
+| `-h`, `--help` | Displays usage information. |
+
+## Options
+
+| Option | Description |
+|-----------------------------------|------------------------------------------------------|
+| `--pem-file ` | Path to your PEM file (use "-" for STDIN) |
+| `--password-file ` | Read the password from this file (use "-" for STDIN) |
+
+## Examples
+
+The `quill decrypt-pem` command is used to convert a password-protected PEM file into a plaintext one.
+
+```sh
+quill decrypt-pem --pem-file identity.pem decrypted.pem
+```
+
+This will interactively prompt for the password. To use it in a script, pass a password file:
+
+```sh
+quill decrypt-pem --pem-file identity.pem --password-file password.txt decrypted.pem
+```
diff --git a/docs/cli-reference/quill-generate.mdx b/docs/cli-reference/quill-generate.mdx
index 90471bf..b06ce6b 100644
--- a/docs/cli-reference/quill-generate.mdx
+++ b/docs/cli-reference/quill-generate.mdx
@@ -24,12 +24,14 @@ quill generate [option]
## Options
-| Option | Description |
-|---------------------------|----------------------------------------------------------|
-| `--pem-file ` | File to write the PEM to. |
-| `--phrase ` | A seed phrase in quotes to use to generate the PEM file. |
-| `--seed-file ` | File to write the seed phrase to [default: seed.txt]. |
-| `--words ` | Number of words: 12 or 24 [default: 12]. |
+| Option | Description |
+|----------------------------------|---------------------------------------------------------------|
+| `--pem-file ` | File to write the PEM to. [default: identity.pem] |
+| `--phrase ` | A seed phrase in quotes to use to generate the PEM file. |
+| `--password-file | Read the encryption password from this file. |
+| `--seed-file ` | File to write the seed phrase to. |
+| `--words ` | Number of words: 12 or 24 [default: 12]. |
+| `--storage-mode ` | Change how PEM files are stored [default: password-protected] |
## Examples
@@ -38,17 +40,35 @@ The `quill generate` command has two primary uses - generating a new key, or rec
To generate a new key, and output it to a PEM file:
```sh
-quill generate --pem-file identity.pem
+quill generate --pem-file identity.pem --seed-file seed.txt
```
-This will generate a new key that you can use to sign IC transactions with quill, or any other IC tool that supports secp256k1, like `dfx`. It will also output a `seed.txt` file containing a seed phrase which can be used to recover this key - write it down in a safe place!
+This will generate a new key that you can use to sign IC transactions with quill, or any other IC tool that supports secp256k1, like `dfx`. It will also output a `seed.txt` file containing a seed phrase which can be used to recover this key - write it down in a safe place! If you do not specify a file, it will be printed to the terminal.
-To recover a key from a seed phrase stored in `phrase.txt`:
+Keys are password-protected by default. This command will prompt for a password interactively. To use it in a script, use `--password-file`:
```sh
-quill generate --phrase "$(< phrase.txt)" --pem-file identity.pem
+quill generate --pem-file identity.pem --password-file password.txt
+```
+
+Or to disable password protection:
+
+```sh
+quill generate --pem-file identity.pem --storage-mode plaintext
+```
+
+To recover a key from a seed phrase stored in `seed.txt`:
+
+```sh
+quill generate --phrase "$(< seed.txt)" --pem-file identity.pem
```
## Remarks
-Most `quill` commands take `--pem-file` and `--seed-file` parameters, for the key used to sign the messages. Only one of these parameters is needed at a time.
+Most `quill` commands take a `--pem-file` parameter, for the key used to sign the messages. If the key is password-protected, it will prompt you for the password, or you can use a `--password-file` parameter.
+
+If a password-protected key needs to be exported for use with another tool such as DFX, use [`quill decrypt-pem`].
+
+Technical notes: Passwords are run through `scrypt(r=8,p=1,n=131072,len=32)`, and then the file is encrypted with AES-256-CBC.
+
+[`quill decrypt-pem`]: quill-decrypt-pem.mdx
diff --git a/docs/cli-reference/quill-parent.mdx b/docs/cli-reference/quill-parent.mdx
index a5d5b4c..d480e61 100644
--- a/docs/cli-reference/quill-parent.mdx
+++ b/docs/cli-reference/quill-parent.mdx
@@ -22,26 +22,26 @@ To see the available subcommands, please refer to the [index page](index.mdx) of
You can use the following optional flags with the `quill` parent command or with any of the `quill` subcommands.
-| Flag | Description |
-|-----------------------------|-------------------------------------------------|
-| `-h`, `--help` | Displays usage information. |
-| `--hsm` | Enables HSM functionality. |
-| `--insecure-local-dev-mode` | Enter local testing mode. |
-| `--qr` | Output the result(s) as UTF-8 QR codes. |
-| `-V`, `--version` | Displays version information. |
-| `--ledger` | Authenticate using a Ledger hardware wallet |
+| Flag | Description |
+|-----------------------------|---------------------------------------------|
+| `-h`, `--help` | Displays usage information. |
+| `--hsm` | Enables HSM functionality. |
+| `--insecure-local-dev-mode` | Enter local testing mode. |
+| `--qr` | Output the result(s) as UTF-8 QR codes. |
+| `-V`, `--version` | Displays version information. |
+| `--ledger` | Authenticate using a Ledger hardware wallet |
## Options
You can use the following options with the `quill` command.
-| Option | Description |
-|-------------------------------|---------------------------------------------|
-| `--hsm-id ` | Specifies the HSM key identifier. |
-| `--hsm-libpath ` | Specifies the path to the HSM library. |
-| `--hsm-slot ` | Specifies the HSM slot to use. |
-| `--pem-file ` | Path to your PEM file (use "-" for STDIN). |
-| `--seed-file ` | Path to your seed file (use "-" for STDIN). |
+| Option | Description |
+|-----------------------------------|------------------------------------------------------------------------------------|
+| `--hsm-id ` | Specifies the HSM key identifier. |
+| `--hsm-libpath ` | Specifies the path to the HSM library. |
+| `--hsm-slot ` | Specifies the HSM slot to use. |
+| `--pem-file ` | Path to your PEM file (use "-" for STDIN). |
+| `--password-file ` | If the PEM file is encrypted, read the password from this file (use "-" for STDIN) |
## Examples
@@ -53,22 +53,29 @@ See [`quill generate`] to generate a new key file, though Quill should be compat
quill list-neurons --pem-file identity.pem
```
-Some commands that do not require your key will still be more useful with it; for example, `quill account-balance` doesn't require authentication, but providing your key prevents you from having to provide your principal or account ID:
+This file can be specified to come from stdin:
```sh
-quill account-balance --pem-file identity.pem
+cat identity.pem | quill list-neurons --pem-file -
```
-Quill can also be used with a seed phrase directly, though using [`quill generate`] to convert it into a private key should be preferred instead. To authenticate using a `seed.txt` file containing your seed phrase:
+If the PEM file is password-protected, this will prompt for the password interactively. To use it in a script, specify a password file:
```sh
-quill list-neurons --seed-file seed.txt
+quill list-neurons --pem-file identity.pem --password-file password.txt
```
-Both of these files can be specified to come from stdin:
+Some commands that do not require your key will still be more useful with it; for example, `quill account-balance` doesn't require authentication, but providing your key prevents you from having to provide your principal or account ID:
```sh
-cat identity.pem | quill list-neurons --pem-file -
+quill account-balance --pem-file identity.pem
+```
+
+Previous versions of Quill could also be used with a seed phrase directly; it must now be converted to a PEM file. To authenticate using a `seed.txt` file containing your seed phrase:
+
+```sh
+quill generate --pem-file identity.pem --phrase "$(cat seed.txt)"
+quill list-neurons --pem-file identity.pem
```
Quill can also sign transactions using a hardware key (HSM) such as Nitrokey or Yubikey. It will need to have been configured beforehand with a secp256r1 (aka P-256) key, and you will need OpenSC or an equivalent installed. Assuming the HSM is in slot 0 (`pkcs11-tool --list-slots`), and you are signing with the first key it holds, such a signing command might look like:
diff --git a/src/commands/account_balance.rs b/src/commands/account_balance.rs
index ab87936..65806e9 100644
--- a/src/commands/account_balance.rs
+++ b/src/commands/account_balance.rs
@@ -4,6 +4,7 @@ use crate::{
get_account_id, ledger_canister_id, AnyhowResult, AuthInfo, ParsedNnsAccount,
ROLE_ICRC1_LEDGER, ROLE_NNS_LEDGER,
},
+ AUTH_FLAGS,
};
use candid::{CandidType, Encode};
use clap::Parser;
@@ -19,7 +20,7 @@ pub struct AccountBalanceArgs {
#[derive(Parser)]
pub struct AccountBalanceOpts {
/// The id of the account to query. Optional if a key is used.
- #[arg(required_unless_present = "auth")]
+ #[arg(required_unless_present_any = AUTH_FLAGS)]
account_id: Option,
#[command(flatten)]
diff --git a/src/commands/ckbtc/balance.rs b/src/commands/ckbtc/balance.rs
index 63efbd8..da5bdc2 100644
--- a/src/commands/ckbtc/balance.rs
+++ b/src/commands/ckbtc/balance.rs
@@ -7,6 +7,7 @@ use crate::{
ckbtc_canister_id, AnyhowResult, AuthInfo, ParsedAccount, ParsedSubaccount,
ROLE_ICRC1_LEDGER,
},
+ AUTH_FLAGS,
};
/// Sends a message to check the provided user's ckBTC balance.
@@ -15,7 +16,7 @@ use crate::{
#[derive(Parser)]
pub struct BalanceOpts {
/// The account to check. Optional if a key is used.
- #[arg(long, required_unless_present = "auth")]
+ #[arg(long, required_unless_present_any = AUTH_FLAGS)]
of: Option,
/// The subaccount of the account to check.
diff --git a/src/commands/ckbtc/update_balance.rs b/src/commands/ckbtc/update_balance.rs
index c864e07..34b22e9 100644
--- a/src/commands/ckbtc/update_balance.rs
+++ b/src/commands/ckbtc/update_balance.rs
@@ -9,13 +9,14 @@ use crate::{
signing::{sign_ingress_with_request_status_query, IngressWithRequestId},
AnyhowResult, AuthInfo, ParsedAccount, ParsedSubaccount, ROLE_CKBTC_MINTER,
},
+ AUTH_FLAGS,
};
/// Signs a message to mint ckBTC from previously deposited BTC.
#[derive(Parser)]
pub struct UpdateBalanceOpts {
/// The account to mint ckBTC to.
- #[arg(long, required_unless_present = "auth")]
+ #[arg(long, required_unless_present_any = AUTH_FLAGS)]
sender: Option,
/// The subaccount to mint ckBTC to.
#[arg(long)]
diff --git a/src/commands/ckbtc/withdrawal_address.rs b/src/commands/ckbtc/withdrawal_address.rs
index 8a7532f..152f102 100644
--- a/src/commands/ckbtc/withdrawal_address.rs
+++ b/src/commands/ckbtc/withdrawal_address.rs
@@ -4,6 +4,7 @@ use clap::Parser;
use crate::{
commands::get_principal,
lib::{AnyhowResult, AuthInfo, ParsedAccount},
+ AUTH_FLAGS,
};
use super::ckbtc_withdrawal_address;
@@ -17,7 +18,7 @@ use super::ckbtc_withdrawal_address;
#[derive(Parser)]
pub struct GetWithdrawalAddressOpts {
/// The principal to get the withdrawal address for. Optional if a key is used.
- #[arg(long, required_unless_present = "auth")]
+ #[arg(long, required_unless_present_any = AUTH_FLAGS)]
of: Option,
/// Uses ckTESTBTC instead of ckBTC.
diff --git a/src/commands/claim_neurons.rs b/src/commands/claim_neurons.rs
index 3253ff8..b836c47 100644
--- a/src/commands/claim_neurons.rs
+++ b/src/commands/claim_neurons.rs
@@ -5,25 +5,18 @@ use crate::lib::{
signing::{sign_ingress_with_request_status_query, IngressWithRequestId},
AnyhowResult, AuthInfo, ROLE_NNS_GTC,
};
-use anyhow::{anyhow, Context};
+use anyhow::anyhow;
use candid::Encode;
use clap::Parser;
-use k256::{elliptic_curve::sec1::ToEncodedPoint, SecretKey};
+use k256::elliptic_curve::sec1::ToEncodedPoint;
/// Claim seed neurons from the Genesis Token Canister.
#[derive(Parser)]
pub struct ClaimNeuronOpts;
pub fn exec(auth: &AuthInfo) -> AnyhowResult> {
- if let AuthInfo::PemFile(pem) = auth {
- let keyinfo = pem::parse_many(pem)?
- .into_iter()
- .find(|p| p.tag == "EC PRIVATE KEY")
- .context("Pem file did not contain sec1 key")?;
- let point = SecretKey::from_sec1_der(&keyinfo.contents)
- .map_err(|e| anyhow!("could not load pem file: {e}"))?
- .public_key()
- .to_encoded_point(false);
+ if let AuthInfo::K256Key(pk) = auth {
+ let point = pk.public_key().to_encoded_point(false);
let sig = Encode!(&hex::encode(point.as_bytes()))?;
Ok(vec![sign_ingress_with_request_status_query(
diff --git a/src/commands/decrypt_pem.rs b/src/commands/decrypt_pem.rs
new file mode 100644
index 0000000..c156523
--- /dev/null
+++ b/src/commands/decrypt_pem.rs
@@ -0,0 +1,32 @@
+use std::path::PathBuf;
+
+use anyhow::bail;
+use clap::Parser;
+use sec1::LineEnding;
+
+use crate::{
+ lib::{AnyhowResult, AuthInfo},
+ write_file,
+};
+
+/// Decrypts an encrypted PEM file for use with other tools.
+#[derive(Parser)]
+pub struct DecryptPemOpts {
+ /// The path to write the decrypted PEM to, or "-" for STDOUT
+ output_path: PathBuf,
+}
+
+pub fn exec(auth: &AuthInfo, opts: DecryptPemOpts) -> AnyhowResult<()> {
+ let AuthInfo::K256Key(pk) = auth else {
+ bail!("--pem-file was not set to an encrypted PEM file")
+ };
+ // technically this permits an unencrypted secp256k1 key, which will just be re-encoded directly
+ // but who cares
+ write_file(
+ &opts.output_path,
+ "PEM",
+ pk.to_sec1_pem(LineEnding::default())?.as_bytes(),
+ )?;
+ eprintln!("Wrote PEM file to {}", opts.output_path.display());
+ Ok(())
+}
diff --git a/src/commands/generate.rs b/src/commands/generate.rs
index 1612672..20a09e7 100644
--- a/src/commands/generate.rs
+++ b/src/commands/generate.rs
@@ -1,9 +1,19 @@
-use crate::lib::{get_account_id, mnemonic_to_pem, AnyhowResult, AuthInfo};
-use anyhow::{anyhow, Context};
+use crate::{
+ lib::{get_account_id, mnemonic_to_key, AnyhowResult},
+ read_file,
+};
+use anyhow::{anyhow, bail, Context};
use bip39::{Language, Mnemonic};
-use clap::Parser;
-use rand::{rngs::OsRng, RngCore};
-use std::path::PathBuf;
+use clap::{Parser, ValueEnum};
+use dialoguer::Password;
+use ic_agent::{identity::Secp256k1Identity, Identity};
+use pkcs8::EncodePrivateKey;
+use rand::{rngs::OsRng, thread_rng, RngCore};
+use sec1::LineEnding;
+use std::{
+ io::{stdin, IsTerminal},
+ path::PathBuf,
+};
/// Generate a mnemonic seed phrase and generate or recover PEM.
#[derive(Parser, Debug)]
@@ -13,13 +23,13 @@ pub struct GenerateOpts {
#[arg(long, default_value = "12")]
words: u32,
- /// File to write the seed phrase to.
- #[arg(long, default_value = "seed.txt")]
- seed_file: PathBuf,
+ /// File to write the seed phrase to. If unspecified, it will be printed to the terminal.
+ #[arg(long)]
+ seed_file: Option,
/// File to write the PEM to.
- #[arg(long)]
- pem_file: Option,
+ #[arg(long, default_value = "identity.pem")]
+ pem_file: PathBuf,
/// A seed phrase in quotes to use to generate the PEM file.
#[arg(long)]
@@ -32,18 +42,44 @@ pub struct GenerateOpts {
/// Overwrite any existing PEM file.
#[arg(long)]
overwrite_pem_file: bool,
+
+ /// Change how PEM files are stored.
+ #[arg(long, value_enum, default_value_t = StorageMode::PasswordProtected)]
+ storage_mode: StorageMode,
+
+ /// Read the encryption password from this file. Use "-" for STDIN. Required if STDIN is being piped.
+ #[arg(long)]
+ password_file: Option,
+}
+
+#[derive(Debug, ValueEnum, Clone, Copy, PartialEq, Eq)]
+enum StorageMode {
+ PasswordProtected,
+ Plaintext,
}
/// Generate or recover mnemonic seed phrase and/or PEM file.
pub fn exec(opts: GenerateOpts) -> AnyhowResult {
- if opts.seed_file.exists() && !opts.overwrite_seed_file {
- return Err(anyhow!("Seed file exists and overwrite is not set."));
- }
- if let Some(path) = &opts.pem_file {
- if path.exists() && !opts.overwrite_pem_file {
- return Err(anyhow!("PEM file exists and overwrite is not set."));
+ if let Some(seed_file) = &opts.seed_file {
+ if !opts.overwrite_seed_file && seed_file.exists() {
+ bail!(
+ "Seed file {} exists and overwrite is not set.",
+ seed_file.display()
+ );
}
}
+ if !opts.overwrite_pem_file && opts.pem_file.exists() {
+ bail!(
+ "PEM file {} exists and overwrite is not set.",
+ opts.pem_file.display()
+ );
+ }
+ if opts.storage_mode == StorageMode::PasswordProtected
+ && opts.password_file.is_none()
+ && !stdin().is_terminal()
+ {
+ bail!("Must use --password-file if using --storage-mode=password-protected and stdin cannot receive terminal input.");
+ }
let bytes = match opts.words {
12 => 16,
24 => 32,
@@ -59,15 +95,43 @@ pub fn exec(opts: GenerateOpts) -> AnyhowResult {
Mnemonic::from_entropy(&key, Language::English).unwrap()
}
};
- let pem = mnemonic_to_pem(&mnemonic).context("Failed to convert mnemonic to PEM")?;
- let mut phrase = mnemonic.into_phrase();
- phrase.push('\n');
- std::fs::write(opts.seed_file, phrase)?;
- if let Some(path) = opts.pem_file {
- std::fs::write(path, &pem)?;
+ let key = mnemonic_to_key(&mnemonic).context("Failed to convert mnemonic to PEM")?;
+ let phrase = mnemonic.into_phrase();
+ if let Some(seed_file) = opts.seed_file {
+ std::fs::write(&seed_file, phrase)?;
+ println!("Written seed file to {}.", seed_file.display());
+ println!("Copy the contents of this file to external media or a piece of paper and store it in a safe place.");
+ if opts.storage_mode != StorageMode::Plaintext {
+ println!("Be sure to delete the file afterwards, as it is not password-protected like the PEM file is.")
+ }
+ } else {
+ println!(
+ "\
+Seed phrase: {phrase}
+Copy this onto a piece of paper or external media and store it in a safe place."
+ );
}
- let principal_id = crate::lib::get_principal(&AuthInfo::PemFile(pem))?;
+ let pem = match opts.storage_mode {
+ StorageMode::Plaintext => key.to_sec1_pem(LineEnding::default())?,
+ StorageMode::PasswordProtected => {
+ let password = if let Some(password_file) = opts.password_file {
+ read_file(password_file, "password")?
+ } else {
+ Password::new()
+ .with_prompt("PEM encryption password")
+ .with_confirmation("Re-enter password", "Passwords did not match")
+ .interact()?
+ };
+ key.to_pkcs8_encrypted_pem(thread_rng(), password, LineEnding::default())?
+ }
+ };
+ std::fs::write(&opts.pem_file, &pem)?;
+ println!("Written PEM file to {}.", opts.pem_file.display());
+ let principal_id = Secp256k1Identity::from_private_key(key)
+ .sender()
+ .map_err(|s| anyhow!(s))?;
let account_id = get_account_id(principal_id, None)?;
+
println!("Principal id: {principal_id}");
println!("Legacy account id: {account_id}");
Ok(())
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index a5c2128..3d25cdc 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -9,6 +9,7 @@ use std::io::{self, Write};
mod account_balance;
mod ckbtc;
mod claim_neurons;
+mod decrypt_pem;
mod generate;
mod get_neuron_info;
mod get_proposal_info;
@@ -44,6 +45,7 @@ pub enum Command {
Ckbtc(ckbtc::CkbtcCommand),
Sns(sns::SnsOpts),
Generate(generate::GenerateOpts),
+ DecryptPem(decrypt_pem::DecryptPemOpts),
/// Print QR Scanner dapp QR code: scan to start dapp to submit QR results.
ScannerQRCode,
QRCode(qrcode::QRCodeOpts),
@@ -95,6 +97,7 @@ pub fn dispatch(auth: &AuthInfo, cmd: Command, fetch_root_key: bool, qr: bool) -
send::exec(opts, fetch_root_key)?;
}
Command::Generate(opts) => generate::exec(opts)?,
+ Command::DecryptPem(opts) => decrypt_pem::exec(auth, opts)?,
Command::Ckbtc(subcmd) => ckbtc::dispatch(auth, subcmd, qr, fetch_root_key)?,
Command::Sns(opts) => sns::dispatch(auth, opts, qr, fetch_root_key)?,
// QR code for URL: https://p5deo-6aaaa-aaaab-aaaxq-cai.raw.ic0.app/
diff --git a/src/commands/public.rs b/src/commands/public.rs
index 7987dbc..ef9b82d 100644
--- a/src/commands/public.rs
+++ b/src/commands/public.rs
@@ -3,7 +3,7 @@ use crate::lib::ledger::LedgerIdentity;
use crate::lib::{
get_account_id, get_principal, AnyhowResult, AuthInfo, ParsedAccount, ParsedSubaccount,
};
-use anyhow::{anyhow, bail, Context};
+use anyhow::{anyhow, bail};
use candid::Principal;
use clap::Parser;
use icp_ledger::AccountIdentifier;
@@ -22,7 +22,7 @@ pub struct PublicOpts {
genesis_dfn: bool,
/// If authenticating with a Ledger device, display the public IDs on the device.
#[cfg_attr(not(feature = "ledger"), arg(hide = true))]
- #[arg(long, requires = "ledgerhq")]
+ #[arg(long, requires = "ledger")]
display_on_ledger: bool,
/// Print IDs for the provided subaccount.
#[arg(long)]
@@ -44,10 +44,10 @@ pub fn exec(auth: &AuthInfo, opts: PublicOpts) -> AnyhowResult {
);
}
if opts.genesis_dfn {
- let AuthInfo::PemFile(pem) = auth else {
- bail!("Must supply a pem or seed file for the DFN address");
+ let AuthInfo::K256Key(pk) = auth else {
+ bail!("Must supply a pem file for the DFN address");
};
- println!("DFN address: {}", get_dfn(pem)?);
+ println!("DFN address: {}", get_dfn(pk.clone())?);
}
if opts.display_on_ledger {
#[cfg(feature = "ledger")]
@@ -91,8 +91,7 @@ fn get_public_ids(
}
}
-fn get_dfn(pem: &str) -> AnyhowResult {
- let pk = SecretKey::from_sec1_pem(pem).context("DFN addresses need a secp256k1 key")?;
+fn get_dfn(pk: SecretKey) -> AnyhowResult {
let pubk = pk.public_key();
let uncompressed = pubk.to_encoded_point(false);
let hash = Keccak256::digest(&uncompressed.as_bytes()[1..]);
diff --git a/src/commands/qrcode.rs b/src/commands/qrcode.rs
index b296380..0098972 100644
--- a/src/commands/qrcode.rs
+++ b/src/commands/qrcode.rs
@@ -12,7 +12,7 @@ pub struct QRCodeOpts {
#[arg(long)]
file: Option,
- // String to be output as a QRCode.
+ /// String to be output as a QRCode.
#[arg(long)]
string: Option,
}
diff --git a/src/commands/sns/balance.rs b/src/commands/sns/balance.rs
index 7d8a829..4b379eb 100644
--- a/src/commands/sns/balance.rs
+++ b/src/commands/sns/balance.rs
@@ -1,7 +1,7 @@
use crate::{
commands::{get_account, send::submit_unsigned_ingress, SendingOpts},
lib::{AuthInfo, ParsedAccount, ParsedSubaccount, ROLE_ICRC1_LEDGER},
- AnyhowResult,
+ AnyhowResult, AUTH_FLAGS,
};
use candid::Encode;
use clap::Parser;
@@ -14,7 +14,7 @@ use super::SnsCanisterIds;
#[derive(Parser)]
pub struct BalanceOpts {
/// The account to query. Optional if a key is used.
- #[arg(long, required_unless_present = "auth")]
+ #[arg(long, required_unless_present_any = AUTH_FLAGS)]
of: Option,
/// The subaccount of the account to query.
diff --git a/src/commands/sns/disburse.rs b/src/commands/sns/disburse.rs
index 56cc8d7..cf2d656 100644
--- a/src/commands/sns/disburse.rs
+++ b/src/commands/sns/disburse.rs
@@ -12,6 +12,7 @@ use crate::{
signing::{sign_ingress_with_request_status_query, IngressWithRequestId},
AnyhowResult, AuthInfo, ParsedAccount, ParsedSubaccount, ROLE_SNS_GOVERNANCE,
},
+ AUTH_FLAGS,
};
use super::{governance_account, ParsedSnsNeuron, SnsCanisterIds};
@@ -22,7 +23,7 @@ pub struct DisburseOpts {
/// The neuron to disburse.
neuron_id: ParsedSnsNeuron,
/// The account to transfer the SNS utility tokens to. If unset, defaults to the caller.
- #[arg(long, required_unless_present = "auth")]
+ #[arg(long, required_unless_present_any = AUTH_FLAGS)]
to: Option,
/// The subaccount to transfer the SNS utility tokens to.
#[arg(long)]
diff --git a/src/commands/sns/disburse_maturity.rs b/src/commands/sns/disburse_maturity.rs
index f861c66..3f23bd1 100644
--- a/src/commands/sns/disburse_maturity.rs
+++ b/src/commands/sns/disburse_maturity.rs
@@ -11,6 +11,7 @@ use crate::{
signing::{sign_ingress_with_request_status_query, IngressWithRequestId},
AnyhowResult, AuthInfo, ParsedAccount, ParsedSubaccount, ROLE_SNS_GOVERNANCE,
},
+ AUTH_FLAGS,
};
use super::{governance_account, ParsedSnsNeuron, SnsCanisterIds};
@@ -21,7 +22,7 @@ pub struct DisburseMaturityOpts {
/// The neuron ID to disburse maturity from.
neuron_id: ParsedSnsNeuron,
/// The account to transfer the SNS utility tokens to. If not provided, defaults to the caller.
- #[arg(long, required_unless_present = "auth")]
+ #[arg(long, required_unless_present_any = AUTH_FLAGS)]
to: Option,
/// The subaccount to transfer the SNS utility tokens to.
#[arg(long)]
diff --git a/src/commands/sns/get_sale_participation.rs b/src/commands/sns/get_sale_participation.rs
index 55a11b1..aee2f9f 100644
--- a/src/commands/sns/get_sale_participation.rs
+++ b/src/commands/sns/get_sale_participation.rs
@@ -5,6 +5,7 @@ use ic_sns_swap::pb::v1::GetBuyerStateRequest;
use crate::{
commands::{get_principal, send::submit_unsigned_ingress, SendingOpts},
lib::{AnyhowResult, AuthInfo, ROLE_SNS_SWAP},
+ AUTH_FLAGS,
};
use super::SnsCanisterIds;
@@ -13,7 +14,7 @@ use super::SnsCanisterIds;
#[derive(Parser)]
pub struct GetSaleParticipationOpts {
/// The principal to query. If unspecified, the caller will be used.
- #[arg(long, required_unless_present = "auth")]
+ #[arg(long, required_unless_present_any = AUTH_FLAGS)]
principal: Option,
#[command(flatten)]
diff --git a/src/commands/sns/neuron_id.rs b/src/commands/sns/neuron_id.rs
index f8c325c..c0aafc5 100644
--- a/src/commands/sns/neuron_id.rs
+++ b/src/commands/sns/neuron_id.rs
@@ -1,5 +1,6 @@
use crate::commands::get_principal;
use crate::lib::{AnyhowResult, AuthInfo};
+use crate::AUTH_FLAGS;
use candid::Principal;
use clap::Parser;
use ic_base_types::PrincipalId;
@@ -9,7 +10,7 @@ use ic_sns_governance::pb::v1::NeuronId;
#[derive(Parser)]
pub struct NeuronIdOpts {
/// Principal used when calculating the SNS Neuron Id.
- #[arg(long, required_unless_present = "auth")]
+ #[arg(long, required_unless_present_any = AUTH_FLAGS)]
principal_id: Option,
/// Memo used when calculating the SNS Neuron Id.
diff --git a/src/lib/ledger.rs b/src/lib/ledger.rs
index 4c6679d..ab77390 100644
--- a/src/lib/ledger.rs
+++ b/src/lib/ledger.rs
@@ -11,12 +11,11 @@ use candid::Principal;
use hidapi::HidApi;
use ic_agent::{agent::EnvelopeContent, Identity, Signature};
use indicatif::ProgressBar;
-use k256::{
- elliptic_curve::sec1::FromEncodedPoint, pkcs8::EncodePublicKey, EncodedPoint, PublicKey,
-};
+use k256::{elliptic_curve::sec1::FromEncodedPoint, EncodedPoint, PublicKey};
use ledger_apdu::{APDUAnswer, APDUCommand, APDUErrorCode};
use ledger_transport_hid::TransportNativeHID;
use once_cell::sync::Lazy;
+use pkcs8::EncodePublicKey;
use serde::Serialize;
use serde_cbor::Serializer;
diff --git a/src/lib/mod.rs b/src/lib/mod.rs
index ba88c29..6d8daf2 100644
--- a/src/lib/mod.rs
+++ b/src/lib/mod.rs
@@ -21,13 +21,10 @@ use ic_nns_constants::{
};
use icp_ledger::{AccountIdentifier, Subaccount};
use icrc_ledger_types::icrc1::account::Account;
-use k256::{elliptic_curve::sec1::ToEncodedPoint, SecretKey};
-use pem::{encode, Pem};
+use k256::SecretKey;
+use ring::signature::Ed25519KeyPair;
use serde_cbor::Value;
-use simple_asn1::ASN1Block::{
- BitString, Explicit, Integer, ObjectIdentifier, OctetString, Sequence,
-};
-use simple_asn1::{oid, to_der, ASN1Class, BigInt, BigUint};
+
#[cfg(feature = "hsm")]
use std::{cell::RefCell, path::PathBuf};
use std::{
@@ -97,11 +94,17 @@ impl HSMInfo {
#[derive(Debug)]
pub enum AuthInfo {
- NoAuth, // No authentication details were provided;
- // only unsigned queries are allowed.
- PemFile(String), // --private-pem file specified
+ /// No authentication details were provided;
+ /// only unsigned queries are allowed.
+ NoAuth,
+ /// Secp256k1 key provided via --pem-file.
+ K256Key(SecretKey),
+ /// Ed25519 key provided via --pem-file. Stored in PKCS#8 because Ed25519KeyPair cannot be cloned or unpacked.
+ Ed25519Key(Vec),
+ /// PKCS#11 security module, usually Nitrokey
#[cfg(feature = "hsm")]
Pkcs11Hsm(HSMInfo),
+ /// Ledger Nano with the Internet Computer app installed
#[cfg(feature = "ledger")]
Ledger,
}
@@ -354,13 +357,10 @@ fn read_pkcs11_pin_env_var() -> Result