diff --git a/.gitignore b/.gitignore index 9677385a4..3a480940e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ stack-local.yaml # Test artefacts /result-* +cardano-crypto-wallet/output cardano-crypto-class/output cardano-crypto-praos/output diff --git a/cardano-base/CHANGELOG.md b/cardano-base/CHANGELOG.md index 57403e8db..0b09ff011 100644 --- a/cardano-base/CHANGELOG.md +++ b/cardano-base/CHANGELOG.md @@ -1,8 +1,9 @@ # Changelog for `cardano-base` -## 0.1.5.1 +## 0.1.6.0 -* +* Re-export `byteArrayFromShortByteString`, `byteArrayToShortByteString`. +* Deprecate `byteStringToByteArray` in favor of newly added `byteArrayFromByteString` ## 0.1.5.0 diff --git a/cardano-base/cardano-base.cabal b/cardano-base/cardano-base.cabal index daaca7a2c..7568c3c46 100644 --- a/cardano-base/cardano-base.cabal +++ b/cardano-base/cardano-base.cabal @@ -1,6 +1,6 @@ cabal-version: 3.0 name: cardano-base -version: 0.1.5.0 +version: 0.1.6.0 synopsis: Various utilities for Cardano description: Various utilities for Cardano. category: diff --git a/cardano-base/src/Cardano/Base/Bytes.hs b/cardano-base/src/Cardano/Base/Bytes.hs index 256719ff1..0082816de 100644 --- a/cardano-base/src/Cardano/Base/Bytes.hs +++ b/cardano-base/src/Cardano/Base/Bytes.hs @@ -2,8 +2,11 @@ {-# LANGUAGE TypeApplications #-} module Cardano.Base.Bytes ( + byteArrayFromByteString, byteArrayToByteString, byteStringToByteArray, + byteArrayFromShortByteString, + byteArrayToShortByteString, slice, splitsAt, ) @@ -22,9 +25,14 @@ byteArrayToByteString :: ByteArray -> ByteString byteArrayToByteString = SBS.fromShort . byteArrayToShortByteString {-# INLINE byteArrayToByteString #-} +byteArrayFromByteString :: ByteString -> ByteArray +byteArrayFromByteString = byteArrayFromShortByteString . SBS.toShort +{-# INLINE byteArrayFromByteString #-} + byteStringToByteArray :: ByteString -> ByteArray byteStringToByteArray = byteArrayFromShortByteString . SBS.toShort {-# INLINE byteStringToByteArray #-} +{-# DEPRECATED byteStringToByteArray "In favor of more consistently named `byteArrayFromByteString`" #-} slice :: Word -> Word -> ByteString -> ByteString slice offset size = diff --git a/cardano-crypto-class/CHANGELOG.md b/cardano-crypto-class/CHANGELOG.md index a92a1e68e..d55b73f37 100644 --- a/cardano-crypto-class/CHANGELOG.md +++ b/cardano-crypto-class/CHANGELOG.md @@ -1,8 +1,9 @@ # Changelog for `cardano-crypto-class` -## 2.5.0.1 +## 2.5.1.0 -* +* Add `psbToByteArray` +* Add `psbFromByteStringM` ## 2.5.0.0 diff --git a/cardano-crypto-class/cardano-crypto-class.cabal b/cardano-crypto-class/cardano-crypto-class.cabal index bab80087d..cdfdc96da 100644 --- a/cardano-crypto-class/cardano-crypto-class.cabal +++ b/cardano-crypto-class/cardano-crypto-class.cabal @@ -1,6 +1,6 @@ cabal-version: 3.0 name: cardano-crypto-class -version: 2.5.0.0 +version: 2.5.1.0 synopsis: Type classes abstracting over cryptography primitives for Cardano @@ -125,7 +125,7 @@ library aeson, base16-bytestring >=1, bytestring, - cardano-base >=0.1.2, + cardano-base >=0.1.6, cardano-binary >=1.7.3, cardano-strict-containers, cborg, diff --git a/cardano-crypto-class/src/Cardano/Crypto/PinnedSizedBytes.hs b/cardano-crypto-class/src/Cardano/Crypto/PinnedSizedBytes.hs index 0c7e02927..f43904e5c 100644 --- a/cardano-crypto-class/src/Cardano/Crypto/PinnedSizedBytes.hs +++ b/cardano-crypto-class/src/Cardano/Crypto/PinnedSizedBytes.hs @@ -20,7 +20,9 @@ module Cardano.Crypto.PinnedSizedBytes ( -- * Conversions psbFromBytes, psbToBytes, + psbToByteArray, psbFromByteString, + psbFromByteStringM, psbFromByteStringCheck, psbToByteString, @@ -76,6 +78,7 @@ import GHC.Exts (Int (..), copyAddrToByteArray#) import GHC.Ptr (Ptr (..)) import qualified Data.ByteString as BS +import qualified Data.ByteString.Short as SBS import qualified Data.Primitive as Prim import Cardano.Crypto.Libsodium.C (c_sodium_compare) @@ -186,8 +189,11 @@ instance KnownNat n => IsString (Code Q (PinnedSizedBytes n)) where psbToBytes :: PinnedSizedBytes n -> [Word8] psbToBytes (PSB ba) = foldrByteArray (:) [] ba +psbToByteArray :: PinnedSizedBytes n -> ByteArray +psbToByteArray (PSB ba) = ba + psbToByteString :: PinnedSizedBytes n -> BS.ByteString -psbToByteString = BS.pack . psbToBytes +psbToByteString = SBS.fromShort . byteArrayToShortByteString . psbToByteArray psbToPackedBytes :: KnownNat n => PinnedSizedBytes n -> PackedBytes n psbToPackedBytes (PSB ba) = packBytes (byteArrayToShortByteString ba) 0 @@ -227,16 +233,28 @@ psbFromByteString bs = Just psb -> psb psbFromByteStringCheck :: forall n. KnownNat n => BS.ByteString -> Maybe (PinnedSizedBytes n) -psbFromByteStringCheck bs - | BS.length bs == size = Just $ +psbFromByteStringCheck = psbFromByteStringM + +psbFromByteStringM :: + forall n m. + (KnownNat n, MonadFail m) => + BS.ByteString -> m (PinnedSizedBytes n) +psbFromByteStringM bs + | n == size = pure $ unsafeDupablePerformIO $ BS.useAsCStringLen bs $ \(Ptr addr#, _) -> do marr@(MutableByteArray marr#) <- newPinnedByteArray size primitive_ $ copyAddrToByteArray# addr# marr# 0# (case size of I# s -> s) arr <- unsafeFreezeByteArray marr return (PSB arr) - | otherwise = Nothing + | otherwise = + fail $ + "Supplied ByteString with size: " + <> show n + <> " did not match the expected number of bytes: " + <> show size where + n = BS.length bs size :: Int size = fromInteger (natVal (Proxy :: Proxy n)) diff --git a/cardano-crypto-class/src/Cardano/Crypto/Util.hs b/cardano-crypto-class/src/Cardano/Crypto/Util.hs index d80f84f8d..4aac4ac79 100644 --- a/cardano-crypto-class/src/Cardano/Crypto/Util.hs +++ b/cardano-crypto-class/src/Cardano/Crypto/Util.hs @@ -33,7 +33,7 @@ module Cardano.Crypto.Util ( ) where -import Cardano.Base.Bytes (byteStringToByteArray) +import Cardano.Base.Bytes (byteArrayFromByteString) import Control.Monad (unless) import Data.Array.Byte (ByteArray (..)) import Data.Bifunctor (first) @@ -120,7 +120,7 @@ naturalToBytes = writeBinaryNatural -- | The inverse of 'bytesToNatural'. Note that this is a naive implementation -- and only suitable for tests. naturalToByteArray :: Int -> Natural -> ByteArray -naturalToByteArray numBytes = byteStringToByteArray . writeBinaryNatural numBytes +naturalToByteArray numBytes = byteArrayFromByteString . writeBinaryNatural numBytes -- | Create a 'Integer' out of a 'ByteString', in big endian. bytesToInteger :: ByteString -> Integer diff --git a/cardano-crypto-wallet/bench/Main.hs b/cardano-crypto-wallet/bench/Main.hs index d5b2f8444..d499fa302 100644 --- a/cardano-crypto-wallet/bench/Main.hs +++ b/cardano-crypto-wallet/bench/Main.hs @@ -49,5 +49,5 @@ main = do , bench "sign-v2 (encryptedSign)" $ whnfIO (encryptedSign key testPass testMsg) , bench "change-passphrase (encryptedChangePass)" $ - whnfIO (encryptedChangePass testPass newPass key) + whnfIO (encryptedChangePassphrase testPass newPass key) ] diff --git a/cardano-crypto-wallet/cardano-crypto-wallet.cabal b/cardano-crypto-wallet/cardano-crypto-wallet.cabal index 76da7500d..f5cbda2e7 100644 --- a/cardano-crypto-wallet/cardano-crypto-wallet.cabal +++ b/cardano-crypto-wallet/cardano-crypto-wallet.cabal @@ -49,9 +49,11 @@ library build-depends: bytestring, + cardano-binary, cardano-crypto-class, cborg, deepseq, + FailT, memory, pkgconfig-depends: libsodium @@ -97,6 +99,7 @@ test-suite tests cardano-crypto-wallet:{cardano-crypto-wallet, testlib}, cborg, hspec, + FailT, benchmark bench import: base, project-config diff --git a/cardano-crypto-wallet/cbits/ed25519/ed25519-donna-batchverify.h b/cardano-crypto-wallet/cbits/ed25519/ed25519-donna-batchverify.h index 43c4923b3..37fa3ce5b 100644 --- a/cardano-crypto-wallet/cbits/ed25519/ed25519-donna-batchverify.h +++ b/cardano-crypto-wallet/cbits/ed25519/ed25519-donna-batchverify.h @@ -202,7 +202,7 @@ ge25519_is_neutral_vartime(const ge25519 *p) { } int -ED25519_FN(ed25519_sign_open_batch) (const unsigned char **m, size_t *mlen, const unsigned char **pk, const unsigned char **RS, size_t num, int *valid) { +CCW_FN(ed25519_sign_open_batch) (const unsigned char **m, size_t *mlen, const unsigned char **pk, const unsigned char **RS, size_t num, int *valid) { batch_heap ALIGN(16) batch; ge25519 ALIGN(16) p; bignum256modm *r_scalars; @@ -217,7 +217,7 @@ ED25519_FN(ed25519_sign_open_batch) (const unsigned char **m, size_t *mlen, cons batchsize = (num > max_batch_size) ? max_batch_size : num; /* generate r (scalars[batchsize+1]..scalars[2*batchsize] */ - ED25519_FN(ed25519_randombytes_unsafe) (batch.r, batchsize * 16); + CCW_FN(ed25519_randombytes_unsafe) (batch.r, batchsize * 16); r_scalars = &batch.scalars[batchsize + 1]; for (i = 0; i < batchsize; i++) expand256_modm(r_scalars[i], batch.r[i], 16); @@ -252,7 +252,7 @@ ED25519_FN(ed25519_sign_open_batch) (const unsigned char **m, size_t *mlen, cons fallback: for (i = 0; i < batchsize; i++) { - valid[i] = ED25519_FN(ed25519_sign_open) (m[i], mlen[i], pk[i], RS[i]) ? 0 : 1; + valid[i] = CCW_FN(ed25519_sign_open) (m[i], mlen[i], pk[i], RS[i]) ? 0 : 1; ret |= (valid[i] ^ 1); } } @@ -266,7 +266,7 @@ ED25519_FN(ed25519_sign_open_batch) (const unsigned char **m, size_t *mlen, cons } for (i = 0; i < num; i++) { - valid[i] = ED25519_FN(ed25519_sign_open) (m[i], mlen[i], pk[i], RS[i]) ? 0 : 1; + valid[i] = CCW_FN(ed25519_sign_open) (m[i], mlen[i], pk[i], RS[i]) ? 0 : 1; ret |= (valid[i] ^ 1); } diff --git a/cardano-crypto-wallet/cbits/ed25519/ed25519-randombytes.h b/cardano-crypto-wallet/cbits/ed25519/ed25519-randombytes.h index fa6a399d4..e9dec8c13 100644 --- a/cardano-crypto-wallet/cbits/ed25519/ed25519-randombytes.h +++ b/cardano-crypto-wallet/cbits/ed25519/ed25519-randombytes.h @@ -1,4 +1,4 @@ void -ED25519_FN(ed25519_randombytes_unsafe) (void *p, size_t len) { +CCW_FN(ed25519_randombytes_unsafe) (void *p, size_t len) { exit(1); } diff --git a/cardano-crypto-wallet/cbits/ed25519/ed25519.c b/cardano-crypto-wallet/cbits/ed25519/ed25519.c index 344f82fe8..8cdc5c951 100644 --- a/cardano-crypto-wallet/cbits/ed25519/ed25519.c +++ b/cardano-crypto-wallet/cbits/ed25519/ed25519.c @@ -5,8 +5,6 @@ */ -#define ED25519_FN(fn) ccw_##fn - #include "ed25519-donna.h" #include "ed25519.h" #include "ed25519-randombytes.h" @@ -37,7 +35,7 @@ ed25519_hram(hash_512bits hram, const ed25519_signature RS, const ed25519_public } void -ED25519_FN(ed25519_publickey) (const ed25519_secret_key sk, ed25519_public_key pk) { +CCW_FN(ed25519_publickey) (const ed25519_secret_key sk, ed25519_public_key pk) { bignum256modm a; ge25519 ALIGN(16) A; hash_512bits extsk = { 0 }; @@ -55,7 +53,7 @@ ED25519_FN(ed25519_publickey) (const ed25519_secret_key sk, ed25519_public_key p } void -ED25519_FN(ed25519_sign) (const unsigned char *m, size_t mlen, const unsigned char *salt, size_t slen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS) { +CCW_FN(ed25519_sign) (const unsigned char *m, size_t mlen, const unsigned char *salt, size_t slen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS) { ed25519_hash_context ctx; bignum256modm r, S, a; ge25519 ALIGN(16) R; @@ -94,7 +92,7 @@ ED25519_FN(ed25519_sign) (const unsigned char *m, size_t mlen, const unsigned ch } int -ED25519_FN(ed25519_sign_open) (const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS) { +CCW_FN(ed25519_sign_open) (const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS) { ge25519 ALIGN(16) R, A; hash_512bits hash; bignum256modm hram, S; @@ -120,7 +118,7 @@ ED25519_FN(ed25519_sign_open) (const unsigned char *m, size_t mlen, const ed2551 /* we only need the leftmost 32 bytes of the extended secret key */ int -ED25519_FN(ed25519_scalar_add) (const ed25519_secret_key sk1, const ed25519_secret_key sk2, ed25519_secret_key res) +CCW_FN(ed25519_scalar_add) (const ed25519_secret_key sk1, const ed25519_secret_key sk2, ed25519_secret_key res) { bignum256modm s1, s2; expand256_modm(s1, sk1, 32); @@ -131,7 +129,7 @@ ED25519_FN(ed25519_scalar_add) (const ed25519_secret_key sk1, const ed25519_secr } int -ED25519_FN(ed25519_point_add) (const ed25519_public_key pk1, const ed25519_public_key pk2, ed25519_public_key res) +CCW_FN(ed25519_point_add) (const ed25519_public_key pk1, const ed25519_public_key pk2, ed25519_public_key res) { ge25519 ALIGN(16) R, P, Q; @@ -148,7 +146,7 @@ ED25519_FN(ed25519_point_add) (const ed25519_public_key pk1, const ed25519_publi } int -ED25519_FN(ed25519_extend) (const ed25519_unextended_secret_key seed, ed25519_secret_key secret) +CCW_FN(ed25519_extend) (const ed25519_unextended_secret_key seed, ed25519_secret_key secret) { ed25519_extsk(secret, seed); diff --git a/cardano-crypto-wallet/cbits/ed25519/ed25519.h b/cardano-crypto-wallet/cbits/ed25519/ed25519.h index 03d4a0a34..ccb0041e8 100644 --- a/cardano-crypto-wallet/cbits/ed25519/ed25519.h +++ b/cardano-crypto-wallet/cbits/ed25519/ed25519.h @@ -3,6 +3,8 @@ #include +#define CCW_FN(fn) cardano_crypto_wallet_##fn + #if defined(__cplusplus) extern "C" { #endif @@ -12,12 +14,12 @@ typedef unsigned char ed25519_public_key[32]; typedef unsigned char ed25519_unextended_secret_key[32]; // this is the UNEXTENDED SECRET KEY typedef unsigned char ed25519_secret_key[64]; // this is the EXTENDED SECRET KEY -void ccw_ed25519_publickey(const ed25519_secret_key sk, ed25519_public_key pk); -int ccw_ed25519_sign_open(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); -void ccw_ed25519_sign (const unsigned char *m, size_t mlen, const unsigned char *salt, size_t slen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS); -int ccw_ed25519_scalar_add (const ed25519_secret_key sk1, const ed25519_secret_key sk2, ed25519_secret_key res); -int ccw_ed25519_point_add (const ed25519_public_key pk1, const ed25519_public_key pk2, ed25519_public_key res); -int ccw_ed25519_extend (const ed25519_unextended_secret_key seed, ed25519_secret_key secret); +void CCW_FN(ed25519_publickey) (const ed25519_secret_key sk, ed25519_public_key pk); +int CCW_FN(ed25519_sign_open) (const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); +void CCW_FN(ed25519_sign) (const unsigned char *m, size_t mlen, const unsigned char *salt, size_t slen, const ed25519_secret_key sk, const ed25519_public_key pk, ed25519_signature RS); +int CCW_FN(ed25519_scalar_add) (const ed25519_secret_key sk1, const ed25519_secret_key sk2, ed25519_secret_key res); +int CCW_FN(ed25519_point_add) (const ed25519_public_key pk1, const ed25519_public_key pk2, ed25519_public_key res); +int CCW_FN(ed25519_extend) (const ed25519_unextended_secret_key seed, ed25519_secret_key secret); #if defined(__cplusplus) } diff --git a/cardano-crypto-wallet/cbits/encrypted_sign.c b/cardano-crypto-wallet/cbits/encrypted_sign.c index dd34219b0..300e1db9e 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -16,97 +16,92 @@ static int ensure_sodium(void) } #define SECRET_KEY_SEED_SIZE 32 -#define ENCRYPTED_KEY_SIZE 64 +#define UNENCRYPTED_KEY_SIZE 64 #define PUBLIC_KEY_SIZE 32 #define CHAIN_CODE_SIZE 32 -#define FULL_KEY_SIZE (ENCRYPTED_KEY_SIZE + PUBLIC_KEY_SIZE + CHAIN_CODE_SIZE) +#define FULL_KEY_SIZE (UNENCRYPTED_KEY_SIZE + PUBLIC_KEY_SIZE + CHAIN_CODE_SIZE) typedef struct { - uint8_t ekey[ENCRYPTED_KEY_SIZE]; + uint8_t skey[UNENCRYPTED_KEY_SIZE]; uint8_t pkey[PUBLIC_KEY_SIZE]; uint8_t cc[CHAIN_CODE_SIZE]; -} encrypted_key; +} key_material; typedef struct { uint8_t pkey[PUBLIC_KEY_SIZE]; uint8_t cc[CHAIN_CODE_SIZE]; } public_key; -/* Store a plaintext (unencrypted) secret key as an encrypted_key struct. - * The ekey field holds the raw secret bytes; callers unwrap with v2 Argon2id +/* Store a plaintext (unencrypted) secret key as an key_material struct. + * The skey field holds the raw secret bytes; callers unwrap with v2 Argon2id * + XChaCha20-Poly1305 at the Haskell layer, not here. */ -static void wallet_encrypted_initialize +static void wallet_initialize (const ed25519_secret_key secret_key, const uint8_t cc[CHAIN_CODE_SIZE], - encrypted_key *out) + key_material *out) { ed25519_public_key pub_key; - ccw_ed25519_publickey(secret_key, pub_key); - memcpy(out->ekey, secret_key, ENCRYPTED_KEY_SIZE); + CCW_FN (ed25519_publickey) (secret_key, pub_key); + memcpy(out->skey, secret_key, UNENCRYPTED_KEY_SIZE); memcpy(out->pkey, pub_key, PUBLIC_KEY_SIZE); memcpy(out->cc, cc, CHAIN_CODE_SIZE); } -int cardano_wallet_encrypted_from_secret +int CCW_FN (from_secret) (const uint8_t seed[SECRET_KEY_SEED_SIZE], const uint8_t cc[CHAIN_CODE_SIZE], - encrypted_key *out) + key_material *out) { ed25519_secret_key secret_key; - if (ccw_ed25519_extend(seed, secret_key)) { + if (CCW_FN (ed25519_extend) (seed, secret_key)) { secure_clear(secret_key, sizeof(secret_key)); return 1; } - wallet_encrypted_initialize(secret_key, cc, out); + wallet_initialize(secret_key, cc, out); secure_clear(secret_key, sizeof(secret_key)); return 0; } -int cardano_wallet_encrypted_new_from_mkg +int CCW_FN (new_from_mkg) (const uint8_t master_key[96], - encrypted_key *out) + key_material *out) { ed25519_secret_key secret_key; memcpy(secret_key, master_key, 64); secret_key[0] &= 248; /* clears the bottom 3 bits */ secret_key[31] &= 0x1F; /* clears the 3 highest bits */ secret_key[31] |= 64; /* set the 2nd highest bit */ - wallet_encrypted_initialize(secret_key, master_key + 64, out); + wallet_initialize(secret_key, master_key + 64, out); secure_clear(secret_key, sizeof(secret_key)); return 0; } -/* Validate that the public key in the struct matches the secret key. +/* Validate that the supplied public key matches the secret key. * Returns 0 on success (keys consistent), 1 on mismatch. */ -int cardano_wallet_encrypted_decrypt - (encrypted_key const *in, - encrypted_key *out) +int CCW_FN (validate) + (const uint8_t skey[UNENCRYPTED_KEY_SIZE], + const uint8_t pkey[PUBLIC_KEY_SIZE]) { ed25519_public_key pub_key; - ccw_ed25519_publickey(in->ekey, pub_key); - if (sodium_memcmp(pub_key, in->pkey, PUBLIC_KEY_SIZE) != 0) { - secure_clear(pub_key, sizeof(pub_key)); + CCW_FN (ed25519_publickey) (skey, pub_key); + if (sodium_memcmp(pub_key, pkey, PUBLIC_KEY_SIZE) != 0) { + secure_clear(pub_key, sizeof(pub_key)); return 1; } - - memcpy(out->ekey, in->ekey, ENCRYPTED_KEY_SIZE); - memcpy(out->pkey, in->pkey, PUBLIC_KEY_SIZE); - memcpy(out->cc, in->cc, CHAIN_CODE_SIZE); - secure_clear(pub_key, sizeof(pub_key)); return 0; } -int cardano_wallet_encrypted_sign - (encrypted_key const *in, +int CCW_FN (sign) + (key_material const *in, uint8_t const *data, uint32_t const data_len, ed25519_signature signature) { ed25519_public_key pub_key; - ccw_ed25519_publickey(in->ekey, pub_key); - ccw_ed25519_sign(data, data_len, in->cc, CHAIN_CODE_SIZE, in->ekey, pub_key, signature); + CCW_FN (ed25519_publickey) (in->skey, pub_key); + CCW_FN (ed25519_sign) (data, data_len, in->cc, CHAIN_CODE_SIZE, in->skey, pub_key, signature); secure_clear(pub_key, sizeof(pub_key)); return 0; } @@ -206,7 +201,7 @@ static void add_left(ed25519_secret_key res_key, uint8_t *z, ed25519_secret_key switch (mode) { case DERIVATION_V1: multiply8_v1(zl8, z, 32); - ccw_ed25519_scalar_add(zl8, priv_key, res_key); + CCW_FN (ed25519_scalar_add) (zl8, priv_key, res_key); break; case DERIVATION_V2: multiply8_v2(zl8, z, 28); @@ -242,14 +237,14 @@ static void add_left_public(uint8_t *out, uint8_t *z, uint8_t *in, derivation_sc break; } - ccw_ed25519_publickey(zl8, pub_zl8); - ccw_ed25519_point_add(pub_zl8, in, out); + CCW_FN (ed25519_publickey) (zl8, pub_zl8); + CCW_FN (ed25519_point_add) (pub_zl8, in, out); } -int cardano_wallet_encrypted_derive_private - (encrypted_key const *in, +int CCW_FN (derive_private) + (key_material const *in, uint32_t index, - encrypted_key *out, + key_material *out, derivation_scheme_mode mode) { ed25519_secret_key priv_key; @@ -259,7 +254,7 @@ int cardano_wallet_encrypted_derive_private uint8_t z[64]; uint8_t hmac_out[64]; - memcpy(priv_key, in->ekey, ENCRYPTED_KEY_SIZE); + memcpy(priv_key, in->skey, UNENCRYPTED_KEY_SIZE); serialize_index32(idxBuf, index, mode); @@ -267,7 +262,7 @@ int cardano_wallet_encrypted_derive_private crypto_auth_hmacsha512_init(&hmac_ctx, in->cc, CHAIN_CODE_SIZE); if (index_is_hardened(index)) { crypto_auth_hmacsha512_update(&hmac_ctx, TAG_DERIVE_Z_HARDENED, 1); - crypto_auth_hmacsha512_update(&hmac_ctx, in->ekey, ENCRYPTED_KEY_SIZE); + crypto_auth_hmacsha512_update(&hmac_ctx, in->skey, UNENCRYPTED_KEY_SIZE); } else { crypto_auth_hmacsha512_update(&hmac_ctx, TAG_DERIVE_Z_NORMAL, 1); crypto_auth_hmacsha512_update(&hmac_ctx, in->pkey, PUBLIC_KEY_SIZE); @@ -282,7 +277,7 @@ int cardano_wallet_encrypted_derive_private crypto_auth_hmacsha512_init(&hmac_ctx, in->cc, CHAIN_CODE_SIZE); if (index_is_hardened(index)) { crypto_auth_hmacsha512_update(&hmac_ctx, TAG_DERIVE_CC_HARDENED, 1); - crypto_auth_hmacsha512_update(&hmac_ctx, in->ekey, ENCRYPTED_KEY_SIZE); + crypto_auth_hmacsha512_update(&hmac_ctx, in->skey, UNENCRYPTED_KEY_SIZE); } else { crypto_auth_hmacsha512_update(&hmac_ctx, TAG_DERIVE_CC_NORMAL, 1); crypto_auth_hmacsha512_update(&hmac_ctx, in->pkey, PUBLIC_KEY_SIZE); @@ -290,10 +285,10 @@ int cardano_wallet_encrypted_derive_private crypto_auth_hmacsha512_update(&hmac_ctx, idxBuf, 4); crypto_auth_hmacsha512_final(&hmac_ctx, hmac_out); - wallet_encrypted_initialize(res_key, hmac_out + 32, out); + wallet_initialize(res_key, hmac_out + 32, out); - secure_clear(priv_key, ENCRYPTED_KEY_SIZE); - secure_clear(res_key, ENCRYPTED_KEY_SIZE); + secure_clear(priv_key, UNENCRYPTED_KEY_SIZE); + secure_clear(res_key, UNENCRYPTED_KEY_SIZE); secure_clear(hmac_out, 64); secure_clear(z, 64); secure_clear(idxBuf, sizeof(idxBuf)); @@ -301,7 +296,7 @@ int cardano_wallet_encrypted_derive_private return 0; } -int cardano_wallet_encrypted_derive_public +int CCW_FN (derive_public) (uint8_t *pub_in, uint8_t *cc_in, uint32_t index, @@ -342,7 +337,7 @@ int cardano_wallet_encrypted_derive_public return 0; } -int wallet_sodium_randombytes(void * const out, size_t const out_len) +int CCW_FN (randombytes) (void * const out, size_t const out_len) { if (ensure_sodium() != 0) { return 1; @@ -351,8 +346,7 @@ int wallet_sodium_randombytes(void * const out, size_t const out_len) return 0; } -int wallet_sodium_argon2id(uint8_t *out, - unsigned long long out_len, +int CCW_FN (argon2id) (uint8_t *out, uint8_t const *pass, unsigned long long pass_len, uint8_t const salt[crypto_pwhash_SALTBYTES], @@ -362,11 +356,8 @@ int wallet_sodium_argon2id(uint8_t *out, if (ensure_sodium() != 0) { return 1; } - if (out_len != 32) { - return 1; - } return crypto_pwhash(out, - out_len, + crypto_aead_xchacha20poly1305_ietf_KEYBYTES, (const char *) pass, pass_len, salt, @@ -375,30 +366,26 @@ int wallet_sodium_argon2id(uint8_t *out, crypto_pwhash_ALG_ARGON2ID13); } -int wallet_sodium_xchacha20poly1305_encrypt( +int CCW_FN (xchacha20poly1305_encrypt) ( uint8_t *ciphertext, uint8_t tag[crypto_aead_xchacha20poly1305_ietf_ABYTES], - uint8_t const *plaintext, - unsigned long long plaintext_len, + uint8_t const secret_to_encrypt[UNENCRYPTED_KEY_SIZE], uint8_t const *aad, unsigned long long aad_len, uint8_t const nonce[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES], uint8_t const key[crypto_aead_xchacha20poly1305_ietf_KEYBYTES]) { unsigned long long clen = 0; - uint8_t combined[crypto_aead_xchacha20poly1305_ietf_ABYTES + ENCRYPTED_KEY_SIZE]; + uint8_t combined[crypto_aead_xchacha20poly1305_ietf_ABYTES + UNENCRYPTED_KEY_SIZE]; if (ensure_sodium() != 0) { return 1; } - if (plaintext_len != ENCRYPTED_KEY_SIZE) { - return 1; - } if (crypto_aead_xchacha20poly1305_ietf_encrypt( combined, &clen, - plaintext, - plaintext_len, + secret_to_encrypt, + UNENCRYPTED_KEY_SIZE, aad, aad_len, NULL, @@ -407,16 +394,15 @@ int wallet_sodium_xchacha20poly1305_encrypt( secure_clear(combined, sizeof(combined)); return 1; } - memcpy(ciphertext, combined, (size_t) plaintext_len); - memcpy(tag, combined + plaintext_len, crypto_aead_xchacha20poly1305_ietf_ABYTES); + memcpy(ciphertext, combined, (size_t) UNENCRYPTED_KEY_SIZE); + memcpy(tag, combined + UNENCRYPTED_KEY_SIZE, clen - UNENCRYPTED_KEY_SIZE); secure_clear(combined, sizeof(combined)); return 0; } -int wallet_sodium_xchacha20poly1305_decrypt( - uint8_t *plaintext, +int CCW_FN (xchacha20poly1305_decrypt) ( + uint8_t *secret_to_decrypt, uint8_t const *ciphertext, - unsigned long long ciphertext_len, uint8_t const tag[crypto_aead_xchacha20poly1305_ietf_ABYTES], uint8_t const *aad, unsigned long long aad_len, @@ -424,22 +410,19 @@ int wallet_sodium_xchacha20poly1305_decrypt( uint8_t const key[crypto_aead_xchacha20poly1305_ietf_KEYBYTES]) { unsigned long long plen = 0; - uint8_t combined[crypto_aead_xchacha20poly1305_ietf_ABYTES + ENCRYPTED_KEY_SIZE]; + uint8_t combined[crypto_aead_xchacha20poly1305_ietf_ABYTES + UNENCRYPTED_KEY_SIZE]; if (ensure_sodium() != 0) { return 1; } - if (ciphertext_len != ENCRYPTED_KEY_SIZE) { - return 1; - } - memcpy(combined, ciphertext, (size_t) ciphertext_len); - memcpy(combined + ciphertext_len, tag, crypto_aead_xchacha20poly1305_ietf_ABYTES); + memcpy(combined, ciphertext, (size_t) UNENCRYPTED_KEY_SIZE); + memcpy(combined + UNENCRYPTED_KEY_SIZE, tag, crypto_aead_xchacha20poly1305_ietf_ABYTES); if (crypto_aead_xchacha20poly1305_ietf_decrypt( - plaintext, + secret_to_decrypt, &plen, NULL, combined, - ciphertext_len + crypto_aead_xchacha20poly1305_ietf_ABYTES, + UNENCRYPTED_KEY_SIZE + crypto_aead_xchacha20poly1305_ietf_ABYTES, aad, aad_len, nonce, @@ -448,5 +431,5 @@ int wallet_sodium_xchacha20poly1305_decrypt( return 1; } secure_clear(combined, sizeof(combined)); - return (plen == ciphertext_len) ? 0 : 1; + return (plen == UNENCRYPTED_KEY_SIZE) ? 0 : 1; } diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 49ce85493..f7f4f04cf 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -1,6 +1,12 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeData #-} +{-# LANGUAGE TypeOperators #-} -- | -- Module : Cardano.Crypto.WalletHD.Encrypted @@ -25,16 +31,64 @@ module Cardano.Crypto.WalletHD.Encrypted ( DerivationScheme (..), DerivationIndex, + -- ** PublicKey + PublicKey, + publicKeySize, + mkPublicKey, + publicKeyByteArray, + publicKeyByteString, + + -- ** Encrypted SecretKey + EncSecretKey, + encSecretKeySize, + mkEncSecretKey, + encSecretKeyByteArray, + encSecretKeyByteString, + + -- ** ChainCode + ChainCode, + chainCodeSize, + mkChainCode, + chainCodeByteArray, + chainCodeByteString, + + -- ** Salt + Salt, + saltSize, + mkSalt, + saltByteArray, + saltByteString, + + -- ** Nonce + Nonce, + nonceSize, + mkNonce, + nonceByteArray, + nonceByteString, + + -- ** Tag + Tag, + tagSize, + mkTag, + tagByteArray, + tagByteString, + + -- ** Envelope + Envelope (eSalt, eNonce, ePublicKey, eChainCode), + encodeEnvelope, + decodeEnvelope, + -- * Construction & validation encryptedCreate, encryptedCreateDirectWithTweak, - encryptedKey, + mkEncryptedKey, unEncryptedKey, + encryptedKey, encryptedKeyFormat, -- * Passphrase operations encryptedValidatePassphrase, - encryptedChangePass, + encryptedChangePassphrase, -- * Signing & derivation encryptedSign, @@ -44,16 +98,30 @@ module Cardano.Crypto.WalletHD.Encrypted ( -- * Accessors encryptedPublic, encryptedChainCode, - encryptedKeyMaterial, -- * Test helpers withFastKdfForTesting, withDeterministicRandomnessForTesting, ) where +import Cardano.Crypto.Libsodium.Memory (zeroMem) +import Cardano.Crypto.PinnedSizedBytes ( + PinnedSizedBytes, + psbCreate, + psbCreateResult, + psbCreateResultLen, + psbFromByteString, + psbFromByteStringM, + psbToByteArray, + psbToByteString, + psbUseAsCPtr, + ) +import Control.Arrow (first) import Control.DeepSeq -import Control.Exception (bracket, finally, mask, onException) +import Control.Exception (bracket) import Control.Monad (when) +import Control.Monad.Trans.Fail.String (errorFail) +import Data.Array.Byte (ByteArray) import Data.Bits (shiftR) import Data.ByteArray (ByteArrayAccess, withByteArray) import qualified Data.ByteArray as B @@ -67,12 +135,22 @@ import Data.IORef ( readIORef, writeIORef, ) +import Data.Proxy (Proxy (..)) import Data.Word import Foreign.C.Types import Foreign.Marshal.Utils (copyBytes) import Foreign.Ptr +import GHC.Stack (HasCallStack) +import GHC.TypeLits import System.IO.Unsafe (unsafePerformIO) +import Cardano.Binary (toCBOR) +import Cardano.Crypto.Libsodium.MLockedBytes ( + MLockedSizedBytes, + mlsbFinalize, + mlsbNew, + mlsbUseAsCPtr, + ) import Codec.CBOR.Decoding ( Decoder, decodeBytes, @@ -80,20 +158,15 @@ import Codec.CBOR.Decoding ( decodeWord, ) import Codec.CBOR.Encoding ( + Encoding, encodeBytes, + encodeInt, encodeListLen, encodeWord, ) import qualified Codec.CBOR.Read as CBOR import qualified Codec.CBOR.Write as CBOR -import Cardano.Crypto.Libsodium.MLockedBytes ( - MLockedSizedBytes, - mlsbFinalize, - mlsbNewZero, - mlsbUseAsCPtr, - ) - -- --------------------------------------------------------------------------- -- Key derivation scheme -- --------------------------------------------------------------------------- @@ -107,22 +180,153 @@ data DerivationScheme = DerivationScheme1 | DerivationScheme2 -- Size constants -- --------------------------------------------------------------------------- -legacyKeySize, publicKeySize, ccSize, signatureSize :: Int -legacyKeySize = 64 -publicKeySize = 32 -ccSize = 32 +signatureSize :: Int signatureSize = 64 -type PublicKey = ByteString -type ChainCode = ByteString -type Salt = ByteString -type Nonce = ByteString -type Ciphertext = ByteString -type AuthenticationTag = ByteString -type AadContext = ByteString +mlsbCreate :: KnownNat n => (MLockedSizedBytes n -> b) -> (b -> IO c) -> IO c +mlsbCreate mkType action = bracket mlsbNew mlsbFinalize (action . mkType) + +------------------------------------------------------------------------------ +-- SECRET_KEY +------------------------------------------------------------------------------ + +-- TODO: Derive from: `UNENCRYPTED_KEY_SIZE` +type SECRET_KEY_SIZE = 64 + +secretKeySize :: Int +secretKeySize = fromInteger (natVal (Proxy @SECRET_KEY_SIZE)) + +-- | Plaintext version of the secret key used for creating signatures +newtype SecretKey = SecretKey {_unSecretKey :: MLockedSizedBytes SECRET_KEY_SIZE} + +newtype SecretKeyPtr = SecretKeyPtr (Ptr Word8) + +withSecretKeyPtr :: SecretKey -> (SecretKeyPtr -> IO a) -> IO a +withSecretKeyPtr (SecretKey secretKey) action = + mlsbUseAsCPtr secretKey (action . SecretKeyPtr) +{-# INLINE withSecretKeyPtr #-} + +-- Encrypted version (same size as decrypted) +type ENC_SECRET_KEY_SIZE = SECRET_KEY_SIZE + +-- | Encrypted version of `SecretKey` +newtype EncSecretKey = EncSecretKey {unEncSecretKey :: PinnedSizedBytes ENC_SECRET_KEY_SIZE} + deriving (Eq, Show) + +newtype EncSecretKeyPtr = EncSecretKeyPtr (Ptr Word8) + +encSecretKeySize :: Int +encSecretKeySize = fromInteger (natVal (Proxy @ENC_SECRET_KEY_SIZE)) + +withEncSecretKeyPtr :: EncSecretKey -> (EncSecretKeyPtr -> IO a) -> IO a +withEncSecretKeyPtr (EncSecretKey encSecretKey) action = + psbUseAsCPtr encSecretKey (action . EncSecretKeyPtr) +{-# INLINE withEncSecretKeyPtr #-} + +mkEncSecretKey :: MonadFail f => ByteString -> f EncSecretKey +mkEncSecretKey bs = EncSecretKey <$> psbFromByteStringM bs + +encSecretKeyByteArray :: EncSecretKey -> ByteArray +encSecretKeyByteArray = psbToByteArray . unEncSecretKey + +encSecretKeyByteString :: EncSecretKey -> ByteString +encSecretKeyByteString = psbToByteString . unEncSecretKey + +encodeEncSecretKey :: EncSecretKey -> Encoding +encodeEncSecretKey = toCBOR . psbToByteArray . unEncSecretKey + +decodeEncSecretKey :: Decoder s EncSecretKey +decodeEncSecretKey = do + saltBytes <- decodeBytes + case mkEncSecretKey saltBytes of + Nothing -> failDecoder XPrvInvalidCiphertextLength + Just salt -> pure salt + +------------------------------------------------------------------------------ +-- PUBLIC_KEY +------------------------------------------------------------------------------ + +type PUBLIC_KEY_SIZE = 32 + +publicKeySize :: Int +publicKeySize = fromInteger (natVal (Proxy @PUBLIC_KEY_SIZE)) -legacyTotalKeySize :: Int -legacyTotalKeySize = legacyKeySize + publicKeySize + ccSize +newtype PublicKey = PublicKey {unPublicKey :: PinnedSizedBytes PUBLIC_KEY_SIZE} + deriving (Eq, Show) +newtype PublicKeyPtr = PublicKeyPtr (Ptr Word8) + +withPublicKeyPtr :: PublicKey -> (PublicKeyPtr -> IO a) -> IO a +withPublicKeyPtr (PublicKey publicKey) action = + psbUseAsCPtr publicKey (action . PublicKeyPtr) +{-# INLINE withPublicKeyPtr #-} + +mkPublicKey :: MonadFail f => ByteString -> f PublicKey +mkPublicKey bs = PublicKey <$> psbFromByteStringM bs + +publicKeyByteArray :: PublicKey -> ByteArray +publicKeyByteArray = psbToByteArray . unPublicKey + +publicKeyByteString :: PublicKey -> ByteString +publicKeyByteString = psbToByteString . unPublicKey + +encodePublicKey :: PublicKey -> Encoding +encodePublicKey = toCBOR . psbToByteArray . unPublicKey + +------------------------------------------------------------------------------ +-- CHAIN_CODE +------------------------------------------------------------------------------ + +type CHAIN_CODE_SIZE = 32 + +chainCodeSize :: Int +chainCodeSize = fromInteger (natVal (Proxy @CHAIN_CODE_SIZE)) + +newtype ChainCode = ChainCode {unChainCode :: PinnedSizedBytes CHAIN_CODE_SIZE} + deriving (Eq, Show) +newtype ChainCodePtr = ChainCodePtr (Ptr Word8) + +withChainCodePtr :: ChainCode -> (ChainCodePtr -> IO a) -> IO a +withChainCodePtr (ChainCode publicKey) action = + psbUseAsCPtr publicKey (action . ChainCodePtr) +{-# INLINE withChainCodePtr #-} + +mkChainCode :: MonadFail f => ByteString -> f ChainCode +mkChainCode bs = ChainCode <$> psbFromByteStringM bs + +chainCodeByteArray :: ChainCode -> ByteArray +chainCodeByteArray = psbToByteArray . unChainCode + +chainCodeByteString :: ChainCode -> ByteString +chainCodeByteString = psbToByteString . unChainCode + +encodeChainCode :: ChainCode -> Encoding +encodeChainCode = toCBOR . psbToByteArray . unChainCode + +------------------------------------------------------------------------------ +-- KEY_MATERIAL +------------------------------------------------------------------------------ + +type data Validity = Validated | Unchecked + +-- | Key material with the secret key in @sodium_malloc@'d locked memory. +data KeyMaterial (v :: Validity) = KeyMaterial + { kmSecretKey :: !SecretKey + , kmPublicKey :: !PublicKey + , kmChainCode :: !ChainCode + } + +type KEY_MATERIAL_SIZE = SECRET_KEY_SIZE + PUBLIC_KEY_SIZE + CHAIN_CODE_SIZE + +keyMaterialSize :: Int +keyMaterialSize = fromInteger (natVal (Proxy @KEY_MATERIAL_SIZE)) + +newtype KeyMaterialBuffer = KeyMaterialBuffer (MLockedSizedBytes KEY_MATERIAL_SIZE) +newtype KeyMaterialPtr = KeyMaterialPtr (Ptr Word8) + +allocaKeyMaterialBuffer :: (KeyMaterialPtr -> IO c) -> IO c +allocaKeyMaterialBuffer action = + mlsbCreate KeyMaterialBuffer $ \(KeyMaterialBuffer keyMaterialBuffer) -> + mlsbUseAsCPtr keyMaterialBuffer (action . KeyMaterialPtr) -- --------------------------------------------------------------------------- -- V2 envelope constants @@ -141,12 +345,11 @@ data KdfParams = KdfParams { kdfMemoryKiB :: !Word , kdfTimeCost :: !Word , kdfParallelism :: !Word - , kdfOutputLength :: !Word } productionKdfParams, fastTestKdfParams :: KdfParams -productionKdfParams = KdfParams 131072 3 4 32 -fastTestKdfParams = KdfParams 4096 1 1 32 +productionKdfParams = KdfParams 131072 3 4 +fastTestKdfParams = KdfParams 4096 1 1 runtimeKdfParamsRef :: IORef KdfParams runtimeKdfParamsRef = unsafePerformIO (newIORef productionKdfParams) @@ -160,12 +363,7 @@ productionArgonMemoryKiB productionArgonMemoryKiB = kdfMemoryKiB productionKdfParams productionArgonTimeCost = kdfTimeCost productionKdfParams productionArgonParallelism = kdfParallelism productionKdfParams -productionArgonOutputLength = kdfOutputLength productionKdfParams - -saltSize, nonceSize, tagSize :: Int -saltSize = 32 -nonceSize = 24 -tagSize = 16 +productionArgonOutputLength = fromIntegral @Int @Word wrappingKeySize -- --------------------------------------------------------------------------- -- Random-mode override (for testing) @@ -237,38 +435,153 @@ newtype EncryptedKey = EncryptedKey ByteString -- V2 envelope data -- --------------------------------------------------------------------------- -data V2Envelope = V2Envelope - { v2Salt :: !Salt - , v2Nonce :: !Nonce - , v2PublicKey :: !PublicKey - , v2ChainCode :: !ChainCode - , v2Ciphertext :: !Ciphertext - , v2Tag :: !AuthenticationTag - } +------------------------------------------------------------------------------ +-- SALT +------------------------------------------------------------------------------ + +type SALT_SIZE = 32 + +saltSize :: Int +saltSize = fromInteger (natVal (Proxy @SALT_SIZE)) + +newtype Salt = Salt {unSalt :: PinnedSizedBytes SALT_SIZE} deriving (Eq, Show) +newtype SaltPtr = SaltPtr (Ptr Word8) --- | Key material with the secret key in @sodium_malloc@'d locked memory. --- The caller who receives a 'KeyMaterial' from a 'decryptKeyMaterial'-style --- call is responsible for calling 'mlsbFinalize' on 'kmSecretKey' when done. -data KeyMaterial = KeyMaterial - { kmSecretKey :: !(MLockedSizedBytes 64) - , kmPublicKey :: !PublicKey - , kmChainCode :: !ChainCode +withSaltPtr :: Salt -> (SaltPtr -> IO a) -> IO a +withSaltPtr (Salt publicKey) action = + psbUseAsCPtr publicKey (action . SaltPtr) +{-# INLINE withSaltPtr #-} + +mkSalt :: MonadFail f => ByteString -> f Salt +mkSalt bs = Salt <$> psbFromByteStringM bs + +saltByteArray :: Salt -> ByteArray +saltByteArray = psbToByteArray . unSalt + +saltByteString :: Salt -> ByteString +saltByteString = psbToByteString . unSalt + +encodeSalt :: Salt -> Encoding +encodeSalt = toCBOR . psbToByteArray . unSalt + +decodeSalt :: Decoder s Salt +decodeSalt = do + saltBytes <- decodeBytes + case mkSalt saltBytes of + Nothing -> failDecoder XPrvInvalidSaltLength + Just salt -> pure salt + +------------------------------------------------------------------------------ +-- NONCE +------------------------------------------------------------------------------ + +-- TODO: Derive from `crypto_aead_xchacha20poly1305_ietf_NPUBBYTES` +type NONCE_SIZE = 24 + +nonceSize :: Int +nonceSize = fromInteger (natVal (Proxy @NONCE_SIZE)) + +newtype Nonce = Nonce {unNonce :: PinnedSizedBytes NONCE_SIZE} + deriving (Eq, Show) +newtype NoncePtr = NoncePtr (Ptr Word8) + +withNoncePtr :: Nonce -> (NoncePtr -> IO a) -> IO a +withNoncePtr (Nonce publicKey) action = + psbUseAsCPtr publicKey (action . NoncePtr) +{-# INLINE withNoncePtr #-} + +mkNonce :: MonadFail f => ByteString -> f Nonce +mkNonce bs = Nonce <$> psbFromByteStringM bs + +nonceByteArray :: Nonce -> ByteArray +nonceByteArray = psbToByteArray . unNonce + +nonceByteString :: Nonce -> ByteString +nonceByteString = psbToByteString . unNonce + +encodeNonce :: Nonce -> Encoding +encodeNonce = toCBOR . psbToByteArray . unNonce + +decodeNonce :: Decoder s Nonce +decodeNonce = do + nonceBytes <- decodeBytes + case mkNonce nonceBytes of + Nothing -> failDecoder XPrvInvalidNonceLength + Just nonce -> pure nonce + +------------------------------------------------------------------------------ +-- TAG +------------------------------------------------------------------------------ + +-- TODO: Derive from: `crypto_aead_xchacha20poly1305_ietf_ABYTES` +type TAG_SIZE = 16 + +tagSize :: Int +tagSize = fromInteger (natVal (Proxy @TAG_SIZE)) + +newtype Tag = Tag {unTag :: PinnedSizedBytes TAG_SIZE} + deriving (Eq, Show) +newtype TagPtr = TagPtr (Ptr Word8) + +withTagPtr :: Tag -> (TagPtr -> IO a) -> IO a +withTagPtr (Tag publicKey) action = + psbUseAsCPtr publicKey (action . TagPtr) +{-# INLINE withTagPtr #-} + +mkTag :: MonadFail f => ByteString -> f Tag +mkTag bs = Tag <$> psbFromByteStringM bs + +tagByteArray :: Tag -> ByteArray +tagByteArray = psbToByteArray . unTag + +tagByteString :: Tag -> ByteString +tagByteString = psbToByteString . unTag + +encodeTag :: Tag -> Encoding +encodeTag = toCBOR . psbToByteArray . unTag + +decodeTag :: Decoder s Tag +decodeTag = do + tagBytes <- decodeBytes + case mkTag tagBytes of + Nothing -> failDecoder XPrvInvalidTagLength + Just tag -> pure tag + +------------------------------------------------------------------------------ +-- WRAPPING_KEY +------------------------------------------------------------------------------ + +-- TODO: Derive from: `crypto_aead_xchacha20poly1305_ietf_KEYBYTES` +type WRAPPING_KEY_SIZE = 32 + +-- | Plaintext version of the wrapping key used for creating signatures +newtype WrappingKey = WrappingKey {_unWrappingKey :: MLockedSizedBytes WRAPPING_KEY_SIZE} + +newtype WrappingKeyPtr = WrappingKeyPtr (Ptr Word8) + +wrappingKeySize :: Int +wrappingKeySize = fromInteger (natVal (Proxy @WRAPPING_KEY_SIZE)) + +withWrappingKeyPtr :: WrappingKey -> (WrappingKeyPtr -> IO a) -> IO a +withWrappingKeyPtr (WrappingKey wrappingKey) action = + mlsbUseAsCPtr wrappingKey (action . WrappingKeyPtr) +{-# INLINE withWrappingKeyPtr #-} + +data Envelope = Envelope + { eSalt :: !Salt + , eNonce :: !Nonce + , ePublicKey :: !PublicKey + , eChainCode :: !ChainCode + , eEncSecretKey :: !EncSecretKey + , eTag :: !Tag } + deriving (Eq, Show) -- FFI pointer newtypes -newtype SecretKeyPtr = SecretKeyPtr (Ptr Word8) newtype MasterKeyPtr = MasterKeyPtr (Ptr Word8) -newtype PublicKeyPtr = PublicKeyPtr (Ptr Word8) -newtype ChainCodePtr = ChainCodePtr (Ptr Word8) -newtype EncryptedKeyPtr = EncryptedKeyPtr (Ptr Word8) newtype SignaturePtr = SignaturePtr (Ptr Word8) newtype PassPhrasePtr = PassPhrasePtr (Ptr Word8) -newtype SaltPtr = SaltPtr (Ptr Word8) -newtype NoncePtr = NoncePtr (Ptr Word8) -newtype TagPtr = TagPtr (Ptr Word8) -newtype CiphertextPtr = CiphertextPtr (Ptr Word8) -newtype WrappingKeyPtr = WrappingKeyPtr (Ptr Word8) type CDerivationScheme = CInt @@ -276,12 +589,26 @@ type CDerivationScheme = CInt -- Public API -- --------------------------------------------------------------------------- +-- | Construct `EncryptedKey` from bytes. +mkEncryptedKey :: ByteString -> Either XPrvError EncryptedKey +mkEncryptedKey bs = + let eKey = EncryptedKey bs + in eKey <$ validateSerializedKey eKey + +-- | In order to promote smoother migration from @cardano-crypto@. Use `mkEncryptedKey` instead encryptedKey :: ByteString -> Either XPrvError EncryptedKey -encryptedKey bs = EncryptedKey bs <$ validateSerializedKey bs +encryptedKey = mkEncryptedKey +{-# DEPRECATED encryptedKey "In favor of `mkEncryptedKey`" #-} + +validateSerializedKey :: EncryptedKey -> Either XPrvError () +validateSerializedKey eKey = + case encryptedKeyFormat eKey of + LegacyV1 -> Right () + EnvelopeV2 -> () <$ decodeEncryptedKeyV2 eKey encryptedKeyFormat :: EncryptedKey -> XPrvFormat encryptedKeyFormat (EncryptedKey bs) - | BS.length bs == legacyTotalKeySize = LegacyV1 + | BS.length bs == keyMaterialSize = LegacyV1 | otherwise = EnvelopeV2 unEncryptedKey :: EncryptedKey -> ByteString @@ -292,72 +619,43 @@ encryptedCreate :: secret -> passphrase -> cc -> IO (Either XPrvError EncryptedKey) encryptedCreate sec pass cc | B.length sec /= 32 = pure (Left XPrvInvalidSecretKey) - | B.length cc /= ccSize = pure (Left XPrvInvalidChainCode) - | otherwise = do - emat <- legacyMaterialFromSecret sec cc - case emat of - Left err -> pure (Left err) - Right mat -> - finally (wrapKeyMaterial pass mat) (mlsbFinalize (kmSecretKey mat)) -{-# NOINLINE encryptedCreate #-} + | B.length cc /= chainCodeSize = pure (Left XPrvInvalidChainCode) + | otherwise = legacyMaterialFromSecret sec cc (wrapKeyMaterial pass) encryptedCreateDirectWithTweak :: (ByteArrayAccess passphrase, ByteArrayAccess secret) => secret -> passphrase -> IO (Either XPrvError EncryptedKey) encryptedCreateDirectWithTweak sec pass | B.length sec /= 96 = pure (Left XPrvInvalidSecretKey) - | otherwise = do - emat <- legacyMaterialFromMasterKey sec - case emat of - Left err -> pure (Left err) - Right mat -> - finally (wrapKeyMaterial pass mat) (mlsbFinalize (kmSecretKey mat)) -{-# NOINLINE encryptedCreateDirectWithTweak #-} + | otherwise = legacyMaterialFromMasterKey sec (wrapKeyMaterial pass) encryptedValidatePassphrase :: ByteArrayAccess passphrase => EncryptedKey -> passphrase -> IO (Either XPrvError ()) -encryptedValidatePassphrase ekey pass = do - emat <- decryptKeyMaterial ekey pass - case emat of - Left err -> pure (Left err) - Right mat -> do - mlsbFinalize (kmSecretKey mat) - pure (Right ()) +encryptedValidatePassphrase eKey pass = + withDecryptedKeyMaterial eKey pass (\_ -> pure $ Right ()) -encryptedChangePass :: +encryptedChangePassphrase :: (ByteArrayAccess oldPassPhrase, ByteArrayAccess newPassPhrase) => oldPassPhrase -> newPassPhrase -> EncryptedKey -> IO (Either XPrvError EncryptedKey) -encryptedChangePass oldPass newPass ekey = - mask $ \restore -> do - emat <- restore (decryptKeyMaterial ekey oldPass) - case emat of - Left err -> pure (Left err) - Right mat -> - restore (wrapKeyMaterial newPass mat) `finally` mlsbFinalize (kmSecretKey mat) +encryptedChangePassphrase oldPass newPass eKey = + withDecryptedKeyMaterial eKey oldPass (wrapKeyMaterial newPass) encryptedSign :: (ByteArrayAccess passphrase, ByteArrayAccess msg) => EncryptedKey -> passphrase -> msg -> IO (Either XPrvError Signature) -encryptedSign ekey pass msg = - mask $ \restore -> do - emat <- restore (decryptKeyMaterial ekey pass) - case emat of - Left err -> pure (Left err) - Right mat -> - restore - ( withLegacyStruct mat $ \legPtr -> do - (status, sig) <- - B.allocRet signatureSize $ \outSig -> - withByteArray msg $ \msgPtr -> - wallet_encrypted_sign - (coerce legPtr) - msgPtr - (fromIntegral $ B.length msg) - (coerce outSig) - pure (if status /= 0 then Left XPrvInternalError else Right (Signature sig)) - ) - `finally` mlsbFinalize (kmSecretKey mat) +encryptedSign eKey pass msg = + withDecryptedKeyMaterial eKey pass $ \keyMaterial -> + withKeyMaterialPtr keyMaterial $ \keyMaterialPtr -> do + (status, sig) <- + B.allocRet signatureSize $ \outSig -> + withByteArray msg $ \msgPtr -> + wallet_sign + keyMaterialPtr + msgPtr + (fromIntegral @Int @CSize $ B.length msg) + (SignaturePtr outSig) + pure (if status /= 0 then Left XPrvInternalError else Right (Signature sig)) encryptedDerivePrivate :: ByteArrayAccess passphrase => @@ -366,97 +664,64 @@ encryptedDerivePrivate :: passphrase -> DerivationIndex -> IO (Either XPrvError EncryptedKey) -encryptedDerivePrivate dscheme ekey pass childIndex = - mask $ \restore -> do - emat <- restore (decryptKeyMaterial ekey pass) - case emat of - Left err -> pure (Left err) - Right parentMat -> - ( do - echildMat <- restore (legacyDerivePrivate dscheme parentMat childIndex) - case echildMat of - Left err -> pure (Left err) - Right childMat -> - restore (wrapKeyMaterial pass childMat) `finally` mlsbFinalize (kmSecretKey childMat) - ) - `finally` mlsbFinalize (kmSecretKey parentMat) +encryptedDerivePrivate dScheme eKey pass childIndex = + withDecryptedKeyMaterial eKey pass $ \parentKeyMaterial -> + legacyDerivePrivate dScheme parentKeyMaterial childIndex (wrapKeyMaterial pass) encryptedDerivePublic :: DerivationScheme -> (PublicKey, ChainCode) -> DerivationIndex -> (PublicKey, ChainCode) -encryptedDerivePublic dscheme (pub, cc) childIndex +encryptedDerivePublic dscheme (publicKey, cc) childIndex | childIndex >= 0x80000000 = error "encryptedDerivePublic: cannot derive hardened key from public key" | otherwise = unsafePerformIO $ do - (newCC, newPub) <- - B.allocRet publicKeySize $ \outPub -> - B.alloc ccSize $ \outCc -> - withByteArray pub $ \ppub -> - withByteArray cc $ \pcc -> do - r <- - wallet_encrypted_derive_public - (coerce ppub) - (coerce pcc) - childIndex - (coerce outPub) - (coerce outCc) - (dschemeToC dscheme) - if r /= 0 - then error "encryptedDerivePublic: hardened index check failed" - else pure () - pure (newPub, newCC) - -encryptedPublic :: EncryptedKey -> ByteString -encryptedPublic (EncryptedKey ekey) = - case encryptedKeyFormat (EncryptedKey ekey) of - LegacyV1 -> sub legacyKeySize publicKeySize ekey - EnvelopeV2 -> either (const badEnvelope) v2PublicKey (decodeV2Envelope ekey) + fmap (first PublicKey) $ psbCreateResult $ \publicKeyPtrOut -> + fmap ChainCode $ psbCreate $ \ccOutPtr -> + withPublicKeyPtr publicKey $ \publicKeyPtr -> + withChainCodePtr cc $ \chainCodePtr -> do + r <- + wallet_derive_public + publicKeyPtr + chainCodePtr + childIndex + (PublicKeyPtr publicKeyPtrOut) + (ChainCodePtr ccOutPtr) + (dschemeToC dscheme) + if r /= 0 + then error "encryptedDerivePublic: hardened index check failed" + else pure () + +encryptedPublic :: HasCallStack => EncryptedKey -> PublicKey +encryptedPublic eKey@(EncryptedKey eKeyBytes) = + case encryptedKeyFormat eKey of + LegacyV1 -> errorFail $ mkPublicKey $ sub secretKeySize publicKeySize eKeyBytes + EnvelopeV2 -> either (const badEnvelope) ePublicKey (decodeEncryptedKeyV2 eKey) where badEnvelope = error "encryptedPublic: invalid v2 envelope" -encryptedChainCode :: EncryptedKey -> ByteString -encryptedChainCode (EncryptedKey ekey) = - case encryptedKeyFormat (EncryptedKey ekey) of - LegacyV1 -> sub (legacyKeySize + publicKeySize) ccSize ekey - EnvelopeV2 -> either (const badEnvelope) v2ChainCode (decodeV2Envelope ekey) +encryptedChainCode :: HasCallStack => EncryptedKey -> ChainCode +encryptedChainCode eKey@(EncryptedKey eKeyBytes) = + case encryptedKeyFormat eKey of + LegacyV1 -> + errorFail $ mkChainCode $ sub (secretKeySize + publicKeySize) chainCodeSize eKeyBytes + EnvelopeV2 -> either (const badEnvelope) eChainCode (decodeEncryptedKeyV2 eKey) where badEnvelope = error "encryptedChainCode: invalid v2 envelope" --- | Decrypt a v2 'EncryptedKey' and return the 64-byte extended ed25519 --- scalar in locked memory. The caller must 'mlsbFinalize' the result when --- done with it. -encryptedKeyMaterial :: - ByteArrayAccess passphrase => - EncryptedKey -> passphrase -> IO (Either XPrvError (MLockedSizedBytes 64)) -encryptedKeyMaterial ekey pass = do - emat <- decryptKeyMaterial ekey pass - case emat of - Left err -> pure (Left err) - Right mat -> pure (Right (kmSecretKey mat)) - --- --------------------------------------------------------------------------- --- Internal: serialization validation --- --------------------------------------------------------------------------- - -validateSerializedKey :: ByteString -> Either XPrvError () -validateSerializedKey bs - | BS.length bs == legacyTotalKeySize = Right () - | otherwise = decodeV2Envelope bs >> pure () - -- --------------------------------------------------------------------------- -- Internal: CBOR V2 envelope codec -- --------------------------------------------------------------------------- -decodeV2Envelope :: ByteString -> Either XPrvError V2Envelope -decodeV2Envelope bs = - case CBOR.deserialiseFromBytes decodeEnvelope (BL.fromStrict bs) of +decodeEncryptedKeyV2 :: EncryptedKey -> Either XPrvError Envelope +decodeEncryptedKeyV2 (EncryptedKey eKeyBytes) = + case CBOR.deserialiseFromBytes decodeEnvelope (BL.fromStrict eKeyBytes) of Right (rest, envelope) | BL.null rest -> Right envelope _ -> Left XPrvDecodeError -decodeEnvelope :: Decoder s V2Envelope +decodeEnvelope :: Decoder s Envelope decodeEnvelope = do decodeListLenOf 9 version <- decodeWord @@ -475,30 +740,26 @@ decodeEnvelope = do || outputLength /= productionArgonOutputLength ) (failDecoder XPrvInvalidKdfParams) - salt <- decodeBytes - when (BS.length salt /= saltSize) (failDecoder XPrvInvalidSaltLength) + salt <- decodeSalt cipherId <- decodeWord when (cipherId /= xchacha20poly1305Id) (failDecoder XPrvUnsupportedCipher) - nonce <- decodeBytes - when (BS.length nonce /= nonceSize) (failDecoder XPrvInvalidNonceLength) + nonce <- decodeNonce aad <- decodeBytes - ciphertext <- decodeBytes - when (BS.length ciphertext /= legacyKeySize) (failDecoder XPrvInvalidCiphertextLength) - tag <- decodeBytes - when (BS.length tag /= tagSize) (failDecoder XPrvInvalidTagLength) + encSecretKey <- decodeEncSecretKey + tag <- decodeTag (pub, cc) <- either failDecoder pure $ decodeAad aad pure $ - V2Envelope - { v2Salt = salt - , v2Nonce = nonce - , v2PublicKey = pub - , v2ChainCode = cc - , v2Ciphertext = ciphertext - , v2Tag = tag + Envelope + { eSalt = salt + , eNonce = nonce + , ePublicKey = pub + , eChainCode = cc + , eEncSecretKey = encSecretKey + , eTag = tag } -encodeV2Envelope :: V2Envelope -> ByteString -encodeV2Envelope envelope = +encodeEnvelope :: Envelope -> ByteString +encodeEnvelope envelope = CBOR.toStrictByteString $ mconcat [ encodeListLen 9 @@ -509,16 +770,16 @@ encodeV2Envelope envelope = , encodeWord productionArgonTimeCost , encodeWord productionArgonParallelism , encodeWord productionArgonOutputLength - , encodeBytes (v2Salt envelope) + , encodeSalt (eSalt envelope) , encodeWord xchacha20poly1305Id - , encodeBytes (v2Nonce envelope) - , encodeBytes (encodeAad (v2PublicKey envelope) (v2ChainCode envelope)) - , encodeBytes (v2Ciphertext envelope) - , encodeBytes (v2Tag envelope) + , encodeNonce (eNonce envelope) + , encodeBytes (encodeAad (ePublicKey envelope) (eChainCode envelope)) + , encodeEncSecretKey (eEncSecretKey envelope) + , encodeTag (eTag envelope) ] -encodeAad :: PublicKey -> ChainCode -> AadContext -encodeAad pub cc = +encodeAad :: PublicKey -> ChainCode -> ByteString +encodeAad publicKey cc = CBOR.toStrictByteString $ mconcat [ encodeListLen 8 @@ -531,12 +792,12 @@ encodeAad pub cc = , encodeWord productionArgonOutputLength , encodeWord xchacha20poly1305Id , encodeWord 1 - , encodeWord (fromIntegral legacyKeySize) - , encodeBytes pub - , encodeBytes cc + , encodeInt secretKeySize + , encodePublicKey publicKey + , encodeChainCode cc ] -decodeAad :: AadContext -> Either XPrvError (PublicKey, ChainCode) +decodeAad :: ByteString -> Either XPrvError (PublicKey, ChainCode) decodeAad bs = case CBOR.deserialiseFromBytes decodeAadFields (BL.fromStrict bs) of Right (rest, result) @@ -567,118 +828,132 @@ decodeAadFields = do payloadKind <- decodeWord when (payloadKind /= 1) (failDecoder XPrvDecodeError) payloadLen <- decodeWord - when (payloadLen /= fromIntegral legacyKeySize) (failDecoder XPrvInvalidCiphertextLength) - pub <- decodeBytes - cc <- decodeBytes - when (BS.length pub /= publicKeySize) (failDecoder XPrvInvalidPublicKey) - when (BS.length cc /= ccSize) (failDecoder XPrvInvalidChainCode) - pure (pub, cc) + when (payloadLen /= fromIntegral @Int @Word encSecretKeySize) $ + failDecoder XPrvInvalidCiphertextLength + pubKeyBytes <- decodeBytes + chainCodeBytes <- decodeBytes + case mkPublicKey pubKeyBytes of + Nothing -> failDecoder XPrvInvalidPublicKey + Just publicKey -> + case mkChainCode chainCodeBytes of + Nothing -> failDecoder XPrvInvalidChainCode + Just chainCode -> pure (publicKey, chainCode) -- --------------------------------------------------------------------------- -- Internal: v2 encrypt / decrypt -- --------------------------------------------------------------------------- -decryptKeyMaterial :: +withDecryptedKeyMaterial :: ByteArrayAccess passphrase => - EncryptedKey -> passphrase -> IO (Either XPrvError KeyMaterial) -decryptKeyMaterial ekey pass = + EncryptedKey -> + passphrase -> + (KeyMaterial Validated -> IO (Either XPrvError a)) -> + IO (Either XPrvError a) +withDecryptedKeyMaterial ekey pass action = case encryptedKeyFormat ekey of LegacyV1 -> pure (Left XPrvDecodeError) - EnvelopeV2 -> v2Decrypt ekey pass - -v2Decrypt :: + EnvelopeV2 -> + mlsbCreate SecretKey $ \secretKey -> + decryptKeyMaterialV2 secretKey ekey pass >>= \case + Left err -> pure $ Left err + Right uncheckedKeyMaterial -> + validateKeyMaterial uncheckedKeyMaterial >>= \case + Left err -> pure $ Left err + Right keyMaterial -> action keyMaterial + +decryptKeyMaterialV2 :: ByteArrayAccess passphrase => - EncryptedKey -> passphrase -> IO (Either XPrvError KeyMaterial) -v2Decrypt (EncryptedKey bs) pass = - case decodeV2Envelope bs of + -- | Empty SecretKey that will be populated from EncryptedKey + SecretKey -> + EncryptedKey -> + passphrase -> + IO (Either XPrvError (KeyMaterial Unchecked)) +decryptKeyMaterialV2 secretKey eKey pass = + case decodeEncryptedKeyV2 eKey of Left err -> pure (Left err) Right envelope -> do - eWrappingKey <- deriveWrappingKey pass (v2Salt envelope) - case eWrappingKey of - Left err -> pure (Left err) - Right wrappingKey -> do - let aad = encodeAad (v2PublicKey envelope) (v2ChainCode envelope) - ptextMlsb <- (mlsbNewZero :: IO (MLockedSizedBytes 64)) - status <- - mlsbUseAsCPtr ptextMlsb $ \ptextPtr -> - withByteArray (v2Ciphertext envelope) $ \ct -> - withByteArray (v2Tag envelope) $ \tg -> - withByteArray aad $ \ad -> - withByteArray (v2Nonce envelope) $ \np -> - withByteArray wrappingKey $ \kp -> - wallet_sodium_xchacha20poly1305_decrypt - (coerce ptextPtr) - (coerce ct) - (fromIntegral @Int @CULLong $ BS.length (v2Ciphertext envelope)) - (coerce tg) - ad - (fromIntegral @Int @CULLong $ BS.length aad) - (coerce np) - (coerce kp) - if status /= 0 - then do - mlsbFinalize ptextMlsb - pure (Left XPrvAuthenticationFailed) - else do - let mat = KeyMaterial ptextMlsb (v2PublicKey envelope) (v2ChainCode envelope) - eVal <- validateKeyMaterial mat `onException` mlsbFinalize ptextMlsb - case eVal of - Left err -> do - mlsbFinalize ptextMlsb - pure (Left err) - Right () -> pure (Right mat) + withWrappingKey pass (eSalt envelope) $ \wrappingKey -> do + let aad = encodeAad (ePublicKey envelope) (eChainCode envelope) + status <- + withSecretKeyPtr secretKey $ \secretKeyPtr -> + withEncSecretKeyPtr (eEncSecretKey envelope) $ \encSecretKeyPtr -> + withTagPtr (eTag envelope) $ \tagPtr -> + withByteArray aad $ \ad -> + withNoncePtr (eNonce envelope) $ \noncePtr -> + withWrappingKeyPtr wrappingKey $ \wrappingKeyPtr -> + wallet_xchacha20poly1305_decrypt + secretKeyPtr + encSecretKeyPtr + tagPtr + ad + (fromIntegral @Int @CULLong $ BS.length aad) + noncePtr + wrappingKeyPtr + if status /= 0 + then + pure $ Left XPrvAuthenticationFailed + else + pure $ + Right $ + KeyMaterial + { kmSecretKey = secretKey + , kmPublicKey = ePublicKey envelope + , kmChainCode = eChainCode envelope + } wrapKeyMaterial :: ByteArrayAccess passphrase => - passphrase -> KeyMaterial -> IO (Either XPrvError EncryptedKey) -wrapKeyMaterial pass material = do - eVal <- validateKeyMaterial material - case eVal of + passphrase -> KeyMaterial Validated -> IO (Either XPrvError EncryptedKey) +wrapKeyMaterial pass KeyMaterial {kmSecretKey, kmPublicKey, kmChainCode} = do + eSalt <- fmap Salt <$> randomBytesIO + eNonce <- fmap Nonce <$> randomBytesIO + case (,) <$> eSalt <*> eNonce of Left err -> pure (Left err) - Right () -> do - eSalt <- randomBytesIO saltSize - eNonce <- randomBytesIO nonceSize - case (,) <$> eSalt <*> eNonce of - Left err -> pure (Left err) - Right (salt, nonce) -> do - eWrappingKey <- deriveWrappingKey pass salt - case eWrappingKey of - Left err -> pure (Left err) - Right wrappingKey -> do - let aad = encodeAad (kmPublicKey material) (kmChainCode material) - mlsbUseAsCPtr (kmSecretKey material) $ \skPtr -> do - ((status, tag), ciphertext) <- - B.allocRet legacyKeySize $ \outCipher -> - B.allocRet tagSize $ \outTag -> - withByteArray aad $ \ad -> - withByteArray nonce $ \np -> - withByteArray wrappingKey $ \kp -> - wallet_sodium_xchacha20poly1305_encrypt - (coerce outCipher) - (coerce outTag) - (coerce skPtr) - (fromIntegral @Int @CULLong legacyKeySize) - ad - (fromIntegral @Int @CULLong $ BS.length aad) - (coerce np) - (coerce kp) - if status /= 0 - then pure (Left XPrvInternalError) - else - pure $ - Right $ - EncryptedKey $ - encodeV2Envelope $ - V2Envelope salt nonce (kmPublicKey material) (kmChainCode material) ciphertext tag - -validateKeyMaterial :: KeyMaterial -> IO (Either XPrvError ()) -validateKeyMaterial mat = - withLegacyStruct mat $ \inPtr -> - bracket (mlsbNewZero :: IO (MLockedSizedBytes 128)) mlsbFinalize $ \outMlsb -> do - r <- - mlsbUseAsCPtr outMlsb $ \outPtr -> - wallet_encrypted_decrypt (coerce inPtr) (coerce outPtr) - pure (if r /= 0 then Left XPrvPublicKeyMismatch else Right ()) + Right (salt, nonce) -> do + withWrappingKey pass salt $ \wrappingKey -> do + let aad = encodeAad kmPublicKey kmChainCode + withSecretKeyPtr kmSecretKey $ \skPtr -> do + (encSecretKey, (tag, status)) <- + fmap (first EncSecretKey) $ psbCreateResult $ \outEncSecretKey -> + fmap (first Tag) $ psbCreateResult $ \outTagPtr -> + withByteArray aad $ \ad -> + withNoncePtr nonce $ \noncePtr -> + withWrappingKeyPtr wrappingKey $ \wrappingKeyPtr -> do + zeroMem outTagPtr (fromIntegral @Int @CSize tagSize) + wallet_xchacha20poly1305_encrypt + (EncSecretKeyPtr outEncSecretKey) + (TagPtr outTagPtr) + skPtr + ad + (fromIntegral @Int @CULLong $ BS.length aad) + noncePtr + wrappingKeyPtr + if status /= 0 + then pure (Left XPrvInternalError) + else + pure $ + Right $ + EncryptedKey $ + encodeEnvelope $ + Envelope + { eSalt = salt + , eNonce = nonce + , ePublicKey = kmPublicKey + , eChainCode = kmChainCode + , eEncSecretKey = encSecretKey + , eTag = tag + } + +-- | Verify that associated public key matches the secret key in the `KeyMaterial` +validateKeyMaterial :: KeyMaterial Unchecked -> IO (Either XPrvError (KeyMaterial Validated)) +validateKeyMaterial KeyMaterial {..} = + withSecretKeyPtr kmSecretKey $ \secretKeyPtr -> do + withPublicKeyPtr kmPublicKey $ \publicKeyPtr -> do + r <- wallet_validate secretKeyPtr publicKeyPtr + pure $ + if r /= 0 + then Left XPrvPublicKeyMismatch + else Right (KeyMaterial {..}) -- --------------------------------------------------------------------------- -- Internal: locked memory helpers @@ -687,37 +962,51 @@ validateKeyMaterial mat = -- | Build a temporary 128-byte locked buffer (ekey || pkey || cc) from -- 'KeyMaterial' and pass a pointer to it to the action. The buffer is zeroed -- and freed when the action returns (normally or via exception). -withLegacyStruct :: KeyMaterial -> (Ptr Word8 -> IO r) -> IO r -withLegacyStruct mat action = - bracket (mlsbNewZero :: IO (MLockedSizedBytes 128)) mlsbFinalize $ \mlsb -> - mlsbUseAsCPtr mlsb $ \base -> do - mlsbUseAsCPtr (kmSecretKey mat) $ \skPtr -> - copyBytes base skPtr 64 - BS.useAsCStringLen (kmPublicKey mat) $ \(pkPtr, _) -> - copyBytes (base `plusPtr` 64) (castPtr pkPtr) 32 - BS.useAsCStringLen (kmChainCode mat) $ \(ccPtr, _) -> - copyBytes (base `plusPtr` 96) (castPtr ccPtr) 32 - action base +withKeyMaterialPtr :: KeyMaterial v -> (KeyMaterialPtr -> IO r) -> IO r +withKeyMaterialPtr KeyMaterial {kmSecretKey, kmPublicKey, kmChainCode} action = + allocaKeyMaterialBuffer $ \ptr@(KeyMaterialPtr keyMaterialPtr) -> do + withSecretKeyPtr kmSecretKey $ \(SecretKeyPtr skPtr) -> + copyBytes keyMaterialPtr skPtr secretKeySize + withPublicKeyPtr kmPublicKey $ \(PublicKeyPtr pkPtr) -> + copyBytes (keyMaterialPtr `plusPtr` secretKeySize) pkPtr publicKeySize + withChainCodePtr kmChainCode $ \(ChainCodePtr ccPtr) -> + copyBytes (keyMaterialPtr `plusPtr` (secretKeySize + publicKeySize)) ccPtr chainCodeSize + action ptr -- | Call a C function that writes a 128-byte @encrypted_key@ struct to the -- pointer it receives, then split the result into 'KeyMaterial'. On failure --- (non-zero return) returns 'Left onFailure'. The caller owns the --- 'MLockedSizedBytes 64' in the returned 'KeyMaterial' and must finalize it. -withEncryptedKeyOutput :: +-- (non-zero return) returns 'Left onFailure'. +withNewKeyMaterial :: XPrvError -> - (Ptr Word8 -> IO CInt) -> - IO (Either XPrvError KeyMaterial) -withEncryptedKeyOutput onFailure action = - bracket (mlsbNewZero :: IO (MLockedSizedBytes 128)) mlsbFinalize $ \outMlsb -> do - r <- mlsbUseAsCPtr outMlsb $ \ptr -> action ptr + -- | Action that will use the newly populated `KeyMaterial` + (KeyMaterial Validated -> IO (Either XPrvError a)) -> + -- | Action that will populate `KeyMaterialPtr` on the C-side, after which it + -- will usable in the `KeyMaterial` for the action above + (KeyMaterialPtr -> IO CInt) -> + IO (Either XPrvError a) +withNewKeyMaterial onFailure keyMaterialAction fillKeyMaterialPtrAction = + allocaKeyMaterialBuffer $ \keyMaterialPtr@(KeyMaterialPtr inPtr) -> do + r <- fillKeyMaterialPtrAction keyMaterialPtr if r /= 0 then pure (Left onFailure) - else mlsbUseAsCPtr outMlsb $ \base -> do - sk <- (mlsbNewZero :: IO (MLockedSizedBytes 64)) - mlsbUseAsCPtr sk $ \skPtr -> copyBytes skPtr base 64 - pub <- BS.packCStringLen (castPtr (base `plusPtr` 64), 32) - cc <- BS.packCStringLen (castPtr (base `plusPtr` 96), 32) - pure (Right (KeyMaterial sk pub cc)) + else mlsbCreate SecretKey $ \secretKey -> do + withSecretKeyPtr secretKey $ \(SecretKeyPtr skPtr) -> copyBytes skPtr inPtr secretKeySize + publicKey <- + psbCreate $ \pkPtr -> + copyBytes pkPtr (inPtr `plusPtr` secretKeySize) publicKeySize + chainCode <- + psbCreate $ \ccPtr -> + copyBytes ccPtr (inPtr `plusPtr` (secretKeySize + publicKeySize)) chainCodeSize + eKeyMaterial <- + validateKeyMaterial $ + KeyMaterial + { kmSecretKey = secretKey + , kmPublicKey = PublicKey publicKey + , kmChainCode = ChainCode chainCode + } + case eKeyMaterial of + Left err -> pure $ Left err + Right keyMaterial -> keyMaterialAction keyMaterial -- --------------------------------------------------------------------------- -- Internal: key-material construction (using C/ed25519) @@ -725,68 +1014,72 @@ withEncryptedKeyOutput onFailure action = legacyMaterialFromSecret :: (ByteArrayAccess secret, ByteArrayAccess cc) => - secret -> cc -> IO (Either XPrvError KeyMaterial) -legacyMaterialFromSecret sec cc = - withEncryptedKeyOutput XPrvInvalidSecretKey $ \outPtr -> + secret -> + cc -> + (KeyMaterial Validated -> IO (Either XPrvError a)) -> + IO (Either XPrvError a) +legacyMaterialFromSecret sec cc action = + withNewKeyMaterial XPrvInvalidSecretKey action $ \outPtr -> withByteArray sec $ \psec -> withByteArray cc $ \pcc -> - wallet_encrypted_from_secret (coerce psec) (coerce pcc) (coerce outPtr) + wallet_from_secret (coerce psec) (coerce pcc) outPtr legacyMaterialFromMasterKey :: - ByteArrayAccess secret => secret -> IO (Either XPrvError KeyMaterial) -legacyMaterialFromMasterKey sec = - withEncryptedKeyOutput XPrvInvalidSecretKey $ \outPtr -> + ByteArrayAccess secret => + secret -> + (KeyMaterial Validated -> IO (Either XPrvError a)) -> + IO (Either XPrvError a) +legacyMaterialFromMasterKey sec action = + withNewKeyMaterial XPrvInvalidSecretKey action $ \outPtr -> withByteArray sec $ \psec -> - wallet_encrypted_new_from_mkg (MasterKeyPtr psec) (coerce outPtr) + wallet_new_from_mkg (MasterKeyPtr psec) outPtr legacyDerivePrivate :: - DerivationScheme -> KeyMaterial -> DerivationIndex -> IO (Either XPrvError KeyMaterial) -legacyDerivePrivate dscheme parent childIndex = - withLegacyStruct parent $ \inPtr -> - withEncryptedKeyOutput XPrvInternalError $ \outPtr -> - wallet_encrypted_derive_private - (coerce inPtr) - childIndex - (coerce outPtr) - (dschemeToC dscheme) + DerivationScheme -> + KeyMaterial Validated -> + DerivationIndex -> + (KeyMaterial Validated -> IO (Either XPrvError a)) -> + IO (Either XPrvError a) +legacyDerivePrivate dscheme parent childIndex action = + withKeyMaterialPtr parent $ \inPtr -> + withNewKeyMaterial XPrvInternalError action $ \outPtr -> + wallet_derive_private inPtr childIndex outPtr (dschemeToC dscheme) -- --------------------------------------------------------------------------- -- Internal: KDF and random bytes -- --------------------------------------------------------------------------- -deriveWrappingKey :: +withWrappingKey :: ByteArrayAccess passphrase => - passphrase -> ByteString -> IO (Either XPrvError B.ScrubbedBytes) -deriveWrappingKey pass salt - | BS.length salt /= saltSize = pure (Left XPrvInvalidSaltLength) - | otherwise = do - params <- readRuntimeKdfParams - let outputLen = fromIntegral (kdfOutputLength params) - memBytes = fromIntegral (kdfMemoryKiB params) * 1024 :: Word64 - (status, key) <- - B.allocRet outputLen $ \out -> - withByteArray pass $ \ppass -> - withByteArray salt $ \psalt -> - wallet_sodium_argon2id - (coerce out) - (fromIntegral @Int @CULLong outputLen) - (coerce ppass) - (fromIntegral @Int @CULLong $ B.length pass) - (coerce psalt) - (fromIntegral @Word @CULLong $ kdfTimeCost params) - (fromIntegral @Word64 @CSize memBytes) - pure (if status == 0 then Right key else Left XPrvInternalError) - -randomBytesIO :: Int -> IO (Either XPrvError ByteString) -randomBytesIO len = do + passphrase -> Salt -> (WrappingKey -> IO (Either XPrvError a)) -> IO (Either XPrvError a) +withWrappingKey pass salt action = do + params <- readRuntimeKdfParams + let memBytes = (fromIntegral @Word @CSize (kdfMemoryKiB params)) * 1024 + passLen = fromIntegral @Int @CULLong (B.length pass) + timeCost = fromIntegral @Word @CULLong (kdfTimeCost params) + mlsbCreate WrappingKey $ \wrappingKey -> + withWrappingKeyPtr wrappingKey $ \outWrappingKeyPtr -> + withByteArray pass $ \passPtr -> + withSaltPtr salt $ \saltPtr -> do + status <- + wallet_argon2id outWrappingKeyPtr (PassPhrasePtr passPtr) passLen saltPtr timeCost memBytes + if status == 0 + then + action wrappingKey + else + pure $ Left XPrvInternalError + +randomBytesIO :: KnownNat n => IO (Either XPrvError (PinnedSizedBytes n)) +randomBytesIO = do mode <- readIORef randomModeRef case mode of SystemRandom -> do - (status, bytes) <- B.allocRet len $ \out -> - wallet_sodium_randombytes out (fromIntegral @Int @CSize len) + (bytes, status) <- psbCreateResultLen wallet_randombytes pure $ if status == 0 then Right bytes else Left XPrvInternalError DeterministicRandom counter -> do - let bytes = deterministicBytes len counter + let + len = fromInteger (natVal bytes) + bytes = psbFromByteString (deterministicBytes len counter) writeIORef randomModeRef (DeterministicRandom (counter + 1)) pure (Right bytes) @@ -795,14 +1088,14 @@ deterministicBytes len counter = BS.pack $ take len $ cycle - [ fromIntegral counter - , fromIntegral (counter `shiftR` 8) - , fromIntegral (counter `shiftR` 16) - , fromIntegral (counter `shiftR` 24) - , fromIntegral (counter `shiftR` 32) - , fromIntegral (counter `shiftR` 40) - , fromIntegral (counter `shiftR` 48) - , fromIntegral (counter `shiftR` 56) + [ fromIntegral @Word64 @Word8 counter + , fromIntegral @Word64 @Word8 (counter `shiftR` 8) + , fromIntegral @Word64 @Word8 (counter `shiftR` 16) + , fromIntegral @Word64 @Word8 (counter `shiftR` 24) + , fromIntegral @Word64 @Word8 (counter `shiftR` 32) + , fromIntegral @Word64 @Word8 (counter `shiftR` 40) + , fromIntegral @Word64 @Word8 (counter `shiftR` 48) + , fromIntegral @Word64 @Word8 (counter `shiftR` 56) ] -- --------------------------------------------------------------------------- @@ -823,43 +1116,43 @@ failDecoder = fail . show -- FFI declarations -- --------------------------------------------------------------------------- -foreign import ccall "cardano_wallet_encrypted_from_secret" - wallet_encrypted_from_secret :: +foreign import ccall "cardano_crypto_wallet_from_secret" + wallet_from_secret :: SecretKeyPtr -> ChainCodePtr -> - EncryptedKeyPtr -> + KeyMaterialPtr -> IO CInt -foreign import ccall "cardano_wallet_encrypted_new_from_mkg" - wallet_encrypted_new_from_mkg :: +foreign import ccall "cardano_crypto_wallet_new_from_mkg" + wallet_new_from_mkg :: MasterKeyPtr -> - EncryptedKeyPtr -> + KeyMaterialPtr -> IO CInt -foreign import ccall "cardano_wallet_encrypted_decrypt" - wallet_encrypted_decrypt :: - EncryptedKeyPtr -> - EncryptedKeyPtr -> +foreign import ccall "cardano_crypto_wallet_validate" + wallet_validate :: + SecretKeyPtr -> + PublicKeyPtr -> IO CInt -foreign import ccall "cardano_wallet_encrypted_sign" - wallet_encrypted_sign :: - EncryptedKeyPtr -> +foreign import ccall "cardano_crypto_wallet_sign" + wallet_sign :: + KeyMaterialPtr -> Ptr Word8 -> - Word32 -> + CSize -> SignaturePtr -> IO CInt -foreign import ccall "cardano_wallet_encrypted_derive_private" - wallet_encrypted_derive_private :: - EncryptedKeyPtr -> +foreign import ccall "cardano_crypto_wallet_derive_private" + wallet_derive_private :: + KeyMaterialPtr -> DerivationIndex -> - EncryptedKeyPtr -> + KeyMaterialPtr -> CDerivationScheme -> IO CInt -foreign import ccall "cardano_wallet_encrypted_derive_public" - wallet_encrypted_derive_public :: +foreign import ccall "cardano_crypto_wallet_derive_public" + wallet_derive_public :: PublicKeyPtr -> ChainCodePtr -> DerivationIndex -> @@ -868,13 +1161,12 @@ foreign import ccall "cardano_wallet_encrypted_derive_public" CDerivationScheme -> IO CInt -foreign import ccall "wallet_sodium_randombytes" - wallet_sodium_randombytes :: Ptr a -> CSize -> IO CInt +foreign import ccall "cardano_crypto_wallet_randombytes" + wallet_randombytes :: Ptr a -> CSize -> IO CInt -foreign import ccall "wallet_sodium_argon2id" - wallet_sodium_argon2id :: +foreign import ccall "cardano_crypto_wallet_argon2id" + wallet_argon2id :: WrappingKeyPtr -> - CULLong -> PassPhrasePtr -> CULLong -> SaltPtr -> @@ -882,23 +1174,21 @@ foreign import ccall "wallet_sodium_argon2id" CSize -> IO CInt -foreign import ccall "wallet_sodium_xchacha20poly1305_encrypt" - wallet_sodium_xchacha20poly1305_encrypt :: - CiphertextPtr -> +foreign import ccall "cardano_crypto_wallet_xchacha20poly1305_encrypt" + wallet_xchacha20poly1305_encrypt :: + EncSecretKeyPtr -> TagPtr -> SecretKeyPtr -> - CULLong -> Ptr Word8 -> CULLong -> NoncePtr -> WrappingKeyPtr -> IO CInt -foreign import ccall "wallet_sodium_xchacha20poly1305_decrypt" - wallet_sodium_xchacha20poly1305_decrypt :: +foreign import ccall "cardano_crypto_wallet_xchacha20poly1305_decrypt" + wallet_xchacha20poly1305_decrypt :: SecretKeyPtr -> - CiphertextPtr -> - CULLong -> + EncSecretKeyPtr -> TagPtr -> Ptr Word8 -> CULLong -> diff --git a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/RoundTripSpec.hs b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/RoundTripSpec.hs index 42e5f8d31..6e4cdf93a 100644 --- a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/RoundTripSpec.hs +++ b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/RoundTripSpec.hs @@ -47,25 +47,25 @@ tests = describe "RoundTrip" $ do r <- encryptedValidatePassphrase key (BS.replicate 32 0x00) r `shouldBe` Left XPrvAuthenticationFailed - it "encryptedChangePass preserves public key" $ do + it "encryptedChangePassphrase preserves public key" $ do res <- encryptedCreate testSeed testPass testCC case res of Left err -> expectationFailure $ "encryptedCreate failed: " ++ show err Right key -> do let newPass = BS.replicate 32 0xFF - res' <- encryptedChangePass testPass newPass key + res' <- encryptedChangePassphrase testPass newPass key case res' of Left err -> expectationFailure $ "changePass failed: " ++ show err Right key' -> encryptedPublic key `shouldBe` encryptedPublic key' - prop "encryptedChangePass roundtrip preserves public key" $ + prop "encryptedChangePassphrase roundtrip preserves public key" $ \(key :: EncryptedKey) -> monadicIO $ do let newPass = BS.replicate 32 0xFF - res1 <- run $ encryptedChangePass emptyPass newPass key + res1 <- run $ encryptedChangePassphrase emptyPass newPass key case res1 of Left err -> pure $ counterexample ("changePass failed: " ++ show err) False Right key' -> do - res2 <- run $ encryptedChangePass newPass emptyPass key' + res2 <- run $ encryptedChangePassphrase newPass emptyPass key' case res2 of Left err -> pure $ counterexample ("change back failed: " ++ show err) False Right key'' -> pure (encryptedPublic key === encryptedPublic key'') @@ -96,9 +96,9 @@ tests = describe "RoundTrip" $ do (pub2, _) = encryptedDerivePublic DerivationScheme2 (pub, cc) 0 pub1 `shouldNotBe` pub2 - prop "encryptedKey . unEncryptedKey is identity" $ + prop "mkEncryptedKey . unEncryptedKey is identity" $ \(key :: EncryptedKey) -> - case encryptedKey (unEncryptedKey key) of + case mkEncryptedKey (unEncryptedKey key) of Left err -> counterexample ("re-parse failed: " ++ show err) False Right key' -> unEncryptedKey key === unEncryptedKey key' diff --git a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/SignSpec.hs b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/SignSpec.hs index 4c814f9e0..ac8faf2c3 100644 --- a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/SignSpec.hs +++ b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/SignSpec.hs @@ -11,21 +11,21 @@ import Test.Hspec import Cardano.Crypto.WalletHD.Encrypted -foreign import ccall "ccw_ed25519_sign_open" - c_ed25519_sign_open :: +foreign import ccall "cardano_crypto_wallet_ed25519_sign_open" + wallet_ed25519_sign_open :: Ptr a -> CSize -> Ptr a -> Ptr a -> IO CInt -verifySignature :: BS.ByteString -> BS.ByteString -> Signature -> Bool -verifySignature pub msg (Signature sig) = unsafePerformIO $ +verifySignature :: PublicKey -> BS.ByteString -> Signature -> Bool +verifySignature publicKey msg (Signature sig) = unsafePerformIO $ BS.useAsCStringLen msg $ \(mp, ml) -> - BS.useAsCString pub $ \pkp -> + BS.useAsCString (publicKeyByteString publicKey) $ \pkp -> BS.useAsCString sig $ \sigp -> (== 0) - <$> c_ed25519_sign_open + <$> wallet_ed25519_sign_open (castPtr mp) (fromIntegral @Int @CSize ml) (castPtr pkp) @@ -79,9 +79,9 @@ tests = describe "Sign" $ do Left err -> expectationFailure $ "encryptedCreate failed: " ++ show err Right key -> do let newPass = BS.replicate 32 0xFF - res' <- encryptedChangePass testPass newPass key + res' <- encryptedChangePassphrase testPass newPass key case res' of - Left err -> expectationFailure $ "encryptedChangePass failed: " ++ show err + Left err -> expectationFailure $ "encryptedChangePassphrase failed: " ++ show err Right key' -> do res'' <- encryptedSign key' newPass testMsg case res'' of @@ -98,8 +98,8 @@ tests = describe "Sign" $ do Left err -> expectationFailure $ "encryptedSign failed: " ++ show err Right sig -> do let newPass = BS.replicate 32 0xFF - res'' <- encryptedChangePass testPass newPass key + res'' <- encryptedChangePassphrase testPass newPass key case res'' of - Left err -> expectationFailure $ "encryptedChangePass failed: " ++ show err + Left err -> expectationFailure $ "encryptedChangePassphrase failed: " ++ show err Right key' -> verifySignature (encryptedPublic key') testMsg sig `shouldBe` True diff --git a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/V2FormatSpec.hs b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/V2FormatSpec.hs index 5996d23f7..1de74e7b2 100644 --- a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/V2FormatSpec.hs +++ b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/V2FormatSpec.hs @@ -2,6 +2,7 @@ module Test.Cardano.Crypto.Wallet.V2FormatSpec (tests) where import qualified Codec.CBOR.Decoding as CBOR import qualified Codec.CBOR.Read as CBOR +import Control.Monad.Trans.Fail.String (errorFail) import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BL import Test.Hspec @@ -23,47 +24,49 @@ wrongPass = BS.replicate 32 0x00 -- --------------------------------------------------------------------------- -- Public-key golden vector -- --- This is the ed25519 public key derived from testSeed via ccw_ed25519_extend --- + ccw_ed25519_publickey. It is fully deterministic (no randomness). +-- This is the ed25519 public key derived from testSeed via cardano_crypto_ed25519_extend +-- + cardano_crypto_ed25519_publickey. It is fully deterministic (no randomness). -- If this changes, the key derivation C code has silently changed. -- --------------------------------------------------------------------------- -expectedPublicKey :: BS.ByteString +expectedPublicKey :: PublicKey expectedPublicKey = - BS.pack - [ 129 - , 57 - , 119 - , 14 - , 168 - , 125 - , 23 - , 95 - , 86 - , 163 - , 84 - , 102 - , 195 - , 76 - , 126 - , 204 - , 203 - , 141 - , 138 - , 145 - , 180 - , 238 - , 55 - , 162 - , 93 - , 246 - , 15 - , 91 - , 143 - , 201 - , 179 - , 148 - ] + errorFail $ + mkPublicKey $ + BS.pack + [ 129 + , 57 + , 119 + , 14 + , 168 + , 125 + , 23 + , 95 + , 86 + , 163 + , 84 + , 102 + , 195 + , 76 + , 126 + , 204 + , 203 + , 141 + , 138 + , 145 + , 180 + , 238 + , 55 + , 162 + , 93 + , 246 + , 15 + , 91 + , 143 + , 201 + , 179 + , 148 + ] tests :: Spec tests = describe "V2Format" $ do @@ -109,7 +112,7 @@ tests = describe "V2Format" $ do Right key -> do let pub = encryptedPublic key cc = encryptedChainCode key - case encryptedKey (unEncryptedKey key) of + case mkEncryptedKey (unEncryptedKey key) of Left err -> expectationFailure $ "re-parse failed: " ++ show err Right key' -> do encryptedPublic key' `shouldBe` pub @@ -117,8 +120,8 @@ tests = describe "V2Format" $ do it "presenting a v1 raw blob returns Left XPrvDecodeError" $ do let v1blob = BS.replicate 128 0x00 - case encryptedKey v1blob of - Left err -> expectationFailure $ "encryptedKey rejected v1 blob: " ++ show err + case mkEncryptedKey v1blob of + Left err -> expectationFailure $ "mkEncryptedKey rejected v1 blob: " ++ show err Right key -> do r <- encryptedValidatePassphrase key testPass r `shouldBe` Left XPrvDecodeError @@ -128,15 +131,15 @@ tests = describe "V2Format" $ do case res of Left err -> expectationFailure $ "encryptedCreate failed: " ++ show err Right key -> - encryptedKey (BS.take 10 (unEncryptedKey key)) + mkEncryptedKey (BS.take 10 (unEncryptedKey key)) `shouldBe` Left XPrvDecodeError - it "encryptedChangePass re-randomizes envelope (different bytes, same public key)" $ do + it "encryptedChangePassphrase re-randomizes envelope (different bytes, same public key)" $ do res <- encryptedCreate testSeed testPass testCC case res of Left err -> expectationFailure $ "encryptedCreate failed: " ++ show err Right key -> do - res' <- encryptedChangePass testPass testPass key + res' <- encryptedChangePassphrase testPass testPass key case res' of Left err -> expectationFailure $ "changePass failed: " ++ show err Right key' -> do