diff --git a/src/lib_crypto/base58.ml b/src/lib_crypto/base58.ml index e78f5cd4d..83e057149 100644 --- a/src/lib_crypto/base58.ml +++ b/src/lib_crypto/base58.ml @@ -320,6 +320,11 @@ module Prefix = struct let secp256k1_secret_key = "\017\162\224\201" (* spsk(54) *) let p256_secret_key = "\016\081\238\189" (* p2sk(54) *) + (* 56 *) + let ed25519_encrypted_seed = "\007\090\060\179\041" (* edesk(88) *) + let secp256k1_encrypted_secret_key = "\009\237\241\174\150" (* spesk(88) *) + let p256_encrypted_secret_key = "\009\048\057\115\171" (* p2esk(88) *) + (* 33 *) let secp256k1_public_key = "\003\254\226\086" (* sppk(55) *) let p256_public_key = "\003\178\139\127" (* p2pk(55) *) diff --git a/src/lib_crypto/base58.mli b/src/lib_crypto/base58.mli index ba33f15e4..5703858e8 100644 --- a/src/lib_crypto/base58.mli +++ b/src/lib_crypto/base58.mli @@ -31,6 +31,10 @@ module Prefix : sig val p256_public_key: string val p256_secret_key: string val p256_signature: string + val ed25519_encrypted_seed: string + val secp256k1_encrypted_secret_key: string + val p256_encrypted_secret_key: string + val generic_signature: string val chain_id: string diff --git a/src/lib_signer_backends/encrypted.ml b/src/lib_signer_backends/encrypted.ml index 405170a57..210951c46 100644 --- a/src/lib_signer_backends/encrypted.ml +++ b/src/lib_signer_backends/encrypted.ml @@ -7,6 +7,10 @@ (* *) (**************************************************************************) +type Base58.data += Encrypted_ed25519 of MBytes.t +type Base58.data += Encrypted_secp256k1 of MBytes.t +type Base58.data += Encrypted_p256 of MBytes.t + open Client_keys let scheme = "encrypted" @@ -19,31 +23,99 @@ module Raw = struct (* Fixed zero nonce *) let nonce = Crypto_box.zero_nonce + (* Secret keys for Ed25519, secp256k1, P256 are 32 bytes long. *) + let encrypted_size = Crypto_box.boxzerobytes + 32 + let pbkdf ~salt ~password = Cstruct.to_bigarray - (Pbkdf.pbkdf2 ~prf:`SHA512 ~count:2048 ~dk_len:32l + (Pbkdf.pbkdf2 ~prf:`SHA512 ~count:32768 ~dk_len:32l ~salt: (Cstruct.of_bigarray salt) ~password: (Cstruct.of_bigarray password)) let encrypt ~password sk = let salt = Rand.generate salt_len in - let key = Crypto_box.Secretbox.unsafe_of_bytes (pbkdf ~password ~salt) in - let msg = Data_encoding.Binary.to_bytes_exn Signature.Secret_key.encoding sk in - let encrypted_passwd = Crypto_box.Secretbox.box key msg nonce in - MBytes.concat "" [ salt ; encrypted_passwd ] - - let decrypt ~password ~encrypted_sk = - let len = MBytes.length encrypted_sk in - let salt = MBytes.sub encrypted_sk 0 salt_len in - let encrypted_sk = MBytes.sub encrypted_sk salt_len (len - salt_len) in let key = Crypto_box.Secretbox.unsafe_of_bytes (pbkdf ~salt ~password) in - match Crypto_box.Secretbox.box_open key encrypted_sk nonce with - | None -> return_none - | Some bytes -> - match Data_encoding.Binary.of_bytes Signature.Secret_key.encoding bytes with - | None -> failwith "Corrupted wallet, deciphered key is invalid" - | Some sk -> return_some sk + let msg = + match (sk : Signature.secret_key) with + | Ed25519 sk -> + Data_encoding.Binary.to_bytes_exn Ed25519.Secret_key.encoding sk + | Secp256k1 sk -> + Data_encoding.Binary.to_bytes_exn Secp256k1.Secret_key.encoding sk + | P256 sk -> + Data_encoding.Binary.to_bytes_exn P256.Secret_key.encoding sk in + MBytes.concat "" [ salt ; + Crypto_box.Secretbox.box key msg nonce ] + let decrypt algo ~password ~encrypted_sk = + let salt = MBytes.sub encrypted_sk 0 salt_len in + let encrypted_sk = + MBytes.sub encrypted_sk salt_len encrypted_size in + let key = Crypto_box.Secretbox.unsafe_of_bytes (pbkdf ~salt ~password) in + match Crypto_box.Secretbox.box_open key encrypted_sk nonce, algo with + | None, _ -> return_none + | Some bytes, Signature.Ed25519 -> begin + match Data_encoding.Binary.of_bytes Ed25519.Secret_key.encoding bytes with + | Some sk -> return_some (Ed25519 sk : Signature.Secret_key.t) + | None -> failwith "Corrupted wallet, deciphered key is not a \ + valid Ed25519 secret key" + end + | Some bytes, Signature.Secp256k1 -> begin + match Data_encoding.Binary.of_bytes Secp256k1.Secret_key.encoding bytes with + | Some sk -> return_some (Secp256k1 sk : Signature.Secret_key.t) + | None -> failwith "Corrupted wallet, deciphered key is not a \ + valid Secp256k1 secret key" + end + | Some bytes, Signature.P256 -> begin + match Data_encoding.Binary.of_bytes P256.Secret_key.encoding bytes with + | Some sk -> return_some (P256 sk : Signature.Secret_key.t) + | None -> failwith "Corrupted wallet, deciphered key is not a \ + valid P256 secret key" + end +end + +module Encodings = struct + + let ed25519 = + let length = + Hacl.Sign.skbytes + Crypto_box.boxzerobytes + Raw.salt_len in + Base58.register_encoding + ~prefix: Base58.Prefix.ed25519_encrypted_seed + ~length + ~to_raw: (fun sk -> MBytes.to_string sk) + ~of_raw: (fun buf -> + if String.length buf <> length then None + else Some (MBytes.of_string buf)) + ~wrap: (fun sk -> Encrypted_ed25519 sk) + + let secp256k1 = + let open Libsecp256k1.External in + let length = + Key.secret_bytes + Crypto_box.boxzerobytes +Raw.salt_len in + Base58.register_encoding + ~prefix: Base58.Prefix.secp256k1_encrypted_secret_key + ~length + ~to_raw: (fun sk -> MBytes.to_string sk) + ~of_raw: (fun buf -> + if String.length buf <> length then None + else Some (MBytes.of_string buf)) + ~wrap: (fun sk -> Encrypted_secp256k1 sk) + + let p256 = + let length = + Uecc.(sk_size secp256r1) + Crypto_box.boxzerobytes + Raw.salt_len in + Base58.register_encoding + ~prefix: Base58.Prefix.p256_encrypted_secret_key + ~length + ~to_raw: (fun sk -> MBytes.to_string sk) + ~of_raw: (fun buf -> + if String.length buf <> length then None + else Some (MBytes.of_string buf)) + ~wrap: (fun sk -> Encrypted_p256 sk) + + let () = + Base58.check_encoded_prefix ed25519 "edesk" 88 ; + Base58.check_encoded_prefix secp256k1 "spesk" 88 ; + Base58.check_encoded_prefix p256 "p2esk" 88 end let decrypted = Hashtbl.create 13 @@ -51,9 +123,8 @@ let passwords = ref [] let rec interactive_decrypt_loop (cctxt : #Client_context.prompter) - ?name ~encrypted_sk = - begin - match name with + ?name ~encrypted_sk algo = + begin match name with | None -> cctxt#prompt_password "Enter password for encrypted key: " @@ -61,30 +132,35 @@ let rec interactive_decrypt_loop cctxt#prompt_password "Enter password for encrypted key \"%s\": " name end >>=? fun password -> - Raw.decrypt ~password ~encrypted_sk >>=? function - | None -> - interactive_decrypt_loop cctxt ?name ~encrypted_sk + Raw.decrypt algo ~password ~encrypted_sk >>=? function | Some sk -> passwords := password :: !passwords ; return sk + | None -> + interactive_decrypt_loop cctxt ?name ~encrypted_sk algo -let rec noninteractice_decrypt_loop ~encrypted_sk = function +let rec noninteractice_decrypt_loop algo ~encrypted_sk = function | [] -> return_none | password :: passwords -> - Raw.decrypt ~password ~encrypted_sk >>=? function - | None -> noninteractice_decrypt_loop ~encrypted_sk passwords + Raw.decrypt algo ~password ~encrypted_sk >>=? function + | None -> noninteractice_decrypt_loop algo ~encrypted_sk passwords | Some sk -> return_some sk let decrypt_payload cctxt ?name encrypted_sk = - match Base58.safe_decode encrypted_sk with - | None -> failwith "Not a Base58 encoded encrypted key" - | Some encrypted_sk -> - let encrypted_sk = MBytes.of_string encrypted_sk in - noninteractice_decrypt_loop ~encrypted_sk !passwords >>=? function - | Some sk -> return sk - | None -> interactive_decrypt_loop cctxt ?name ~encrypted_sk + begin match Base58.decode encrypted_sk with + | Some (Encrypted_ed25519 encrypted_sk) -> + return (Signature.Ed25519, encrypted_sk) + | Some (Encrypted_secp256k1 encrypted_sk) -> + return (Signature.Secp256k1, encrypted_sk) + | Some (Encrypted_p256 encrypted_sk) -> + return (Signature.P256, encrypted_sk) + | _ -> failwith "Not a Base58Check-encoded encrypted key" + end >>=? fun (algo, encrypted_sk) -> + noninteractice_decrypt_loop algo ~encrypted_sk !passwords >>=? function + | Some sk -> return sk + | None -> interactive_decrypt_loop cctxt ?name ~encrypted_sk algo -let decrypt cctxt ?name sk_uri = +let decrypt (cctxt : #Client_context.prompter) ?name sk_uri = let payload = Uri.path (sk_uri : sk_uri :> Uri.t) in decrypt_payload cctxt ?name payload >>=? fun sk -> Hashtbl.replace decrypted sk_uri sk ; @@ -114,7 +190,11 @@ let rec read_passphrase (cctxt : #Client_context.io) = let encrypt cctxt sk = read_passphrase cctxt >>=? fun password -> let payload = Raw.encrypt ~password sk in - let path = Base58.safe_encode (MBytes.to_string payload) in + let encoding = match sk with + | Ed25519 _ -> Encodings.ed25519 + | Secp256k1 _ -> Encodings.secp256k1 + | P256 _ -> Encodings.p256 in + let path = Base58.simple_encode encoding payload in let sk_uri = Client_keys.make_sk_uri (Uri.make ~scheme ~path ()) in Hashtbl.replace decrypted sk_uri sk ; return sk_uri diff --git a/src/lib_signer_backends/encrypted.mli b/src/lib_signer_backends/encrypted.mli index 54f4d5bff..48f369aa6 100644 --- a/src/lib_signer_backends/encrypted.mli +++ b/src/lib_signer_backends/encrypted.mli @@ -10,7 +10,7 @@ module Make(C : sig val cctxt: Client_context.prompter end) : Client_keys.SIGNER val decrypt: - #Client_context.io_wallet -> + #Client_context.prompter -> ?name:string -> Client_keys.sk_uri -> Signature.secret_key tzresult Lwt.t