From 91d2b9aee860a02f0d65bf5806e4d4292abd2b0c Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Thu, 4 Jun 2026 18:43:12 +0300 Subject: [PATCH 01/33] Revert "cardano-crypto-wallet: rename ed25519 C symbols from cardano_crypto_ to ccw_" This reverts commit cc5f867fac0e84466ab0bd3641ea82348eea965d. --- cardano-crypto-wallet/cbits/ed25519/ed25519.c | 2 +- cardano-crypto-wallet/cbits/ed25519/ed25519.h | 12 ++++++------ cardano-crypto-wallet/cbits/encrypted_sign.c | 16 ++++++++-------- .../test/Test/Cardano/Crypto/Wallet/SignSpec.hs | 2 +- .../Test/Cardano/Crypto/Wallet/V2FormatSpec.hs | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cardano-crypto-wallet/cbits/ed25519/ed25519.c b/cardano-crypto-wallet/cbits/ed25519/ed25519.c index 344f82fe8..ec07ec144 100644 --- a/cardano-crypto-wallet/cbits/ed25519/ed25519.c +++ b/cardano-crypto-wallet/cbits/ed25519/ed25519.c @@ -5,7 +5,7 @@ */ -#define ED25519_FN(fn) ccw_##fn +#define ED25519_FN(fn) cardano_crypto_##fn #include "ed25519-donna.h" #include "ed25519.h" diff --git a/cardano-crypto-wallet/cbits/ed25519/ed25519.h b/cardano-crypto-wallet/cbits/ed25519/ed25519.h index 03d4a0a34..3f47269d5 100644 --- a/cardano-crypto-wallet/cbits/ed25519/ed25519.h +++ b/cardano-crypto-wallet/cbits/ed25519/ed25519.h @@ -12,12 +12,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 cardano_crypto_ed25519_publickey(const ed25519_secret_key sk, ed25519_public_key pk); +int cardano_crypto_ed25519_sign_open(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); +void cardano_crypto_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 cardano_crypto_ed25519_scalar_add (const ed25519_secret_key sk1, const ed25519_secret_key sk2, ed25519_secret_key res); +int cardano_crypto_ed25519_point_add (const ed25519_public_key pk1, const ed25519_public_key pk2, ed25519_public_key res); +int cardano_crypto_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..f68a6b8e8 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -42,7 +42,7 @@ static void wallet_encrypted_initialize encrypted_key *out) { ed25519_public_key pub_key; - ccw_ed25519_publickey(secret_key, pub_key); + cardano_crypto_ed25519_publickey(secret_key, pub_key); memcpy(out->ekey, secret_key, ENCRYPTED_KEY_SIZE); memcpy(out->pkey, pub_key, PUBLIC_KEY_SIZE); memcpy(out->cc, cc, CHAIN_CODE_SIZE); @@ -54,7 +54,7 @@ int cardano_wallet_encrypted_from_secret encrypted_key *out) { ed25519_secret_key secret_key; - if (ccw_ed25519_extend(seed, secret_key)) { + if (cardano_crypto_ed25519_extend(seed, secret_key)) { secure_clear(secret_key, sizeof(secret_key)); return 1; } @@ -85,7 +85,7 @@ int cardano_wallet_encrypted_decrypt { ed25519_public_key pub_key; - ccw_ed25519_publickey(in->ekey, pub_key); + cardano_crypto_ed25519_publickey(in->ekey, pub_key); if (sodium_memcmp(pub_key, in->pkey, PUBLIC_KEY_SIZE) != 0) { secure_clear(pub_key, sizeof(pub_key)); return 1; @@ -105,8 +105,8 @@ int cardano_wallet_encrypted_sign 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); + cardano_crypto_ed25519_publickey(in->ekey, pub_key); + cardano_crypto_ed25519_sign(data, data_len, in->cc, CHAIN_CODE_SIZE, in->ekey, pub_key, signature); secure_clear(pub_key, sizeof(pub_key)); return 0; } @@ -206,7 +206,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); + cardano_crypto_ed25519_scalar_add(zl8, priv_key, res_key); break; case DERIVATION_V2: multiply8_v2(zl8, z, 28); @@ -242,8 +242,8 @@ 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); + cardano_crypto_ed25519_publickey(zl8, pub_zl8); + cardano_crypto_ed25519_point_add(pub_zl8, in, out); } int cardano_wallet_encrypted_derive_private 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..356188dfe 100644 --- a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/SignSpec.hs +++ b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/SignSpec.hs @@ -11,7 +11,7 @@ import Test.Hspec import Cardano.Crypto.WalletHD.Encrypted -foreign import ccall "ccw_ed25519_sign_open" +foreign import ccall "cardano_crypto_ed25519_sign_open" c_ed25519_sign_open :: Ptr a -> CSize -> 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..8d6030b10 100644 --- a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/V2FormatSpec.hs +++ b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/V2FormatSpec.hs @@ -23,8 +23,8 @@ 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. -- --------------------------------------------------------------------------- From 7f98af19377af54b28d0642fe329d704284e614f Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sat, 30 May 2026 19:53:53 +0300 Subject: [PATCH 02/33] Re-export `byteArrayFromShortByteString`, `byteArrayToShortByteString`. --- cardano-base/CHANGELOG.md | 4 ++-- cardano-base/cardano-base.cabal | 2 +- cardano-base/src/Cardano/Base/Bytes.hs | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cardano-base/CHANGELOG.md b/cardano-base/CHANGELOG.md index 57403e8db..d5602b7d9 100644 --- a/cardano-base/CHANGELOG.md +++ b/cardano-base/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog for `cardano-base` -## 0.1.5.1 +## 0.1.6.0 -* +* Re-export `byteArrayFromShortByteString`, `byteArrayToShortByteString`. ## 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..dd2e2fdf1 100644 --- a/cardano-base/src/Cardano/Base/Bytes.hs +++ b/cardano-base/src/Cardano/Base/Bytes.hs @@ -4,6 +4,8 @@ module Cardano.Base.Bytes ( byteArrayToByteString, byteStringToByteArray, + byteArrayFromShortByteString, + byteArrayToShortByteString, slice, splitsAt, ) From 4397c906beeee37460e6b0020a07bc9641ce0c78 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sat, 30 May 2026 19:57:12 +0300 Subject: [PATCH 03/33] Add `byteArrayFromByteString` Also deprecate `byteStringToByteArray` in its favor --- cardano-base/CHANGELOG.md | 1 + cardano-base/src/Cardano/Base/Bytes.hs | 6 ++++++ cardano-crypto-class/cardano-crypto-class.cabal | 4 ++-- cardano-crypto-class/src/Cardano/Crypto/Util.hs | 4 ++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cardano-base/CHANGELOG.md b/cardano-base/CHANGELOG.md index d5602b7d9..0b09ff011 100644 --- a/cardano-base/CHANGELOG.md +++ b/cardano-base/CHANGELOG.md @@ -3,6 +3,7 @@ ## 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/src/Cardano/Base/Bytes.hs b/cardano-base/src/Cardano/Base/Bytes.hs index dd2e2fdf1..0082816de 100644 --- a/cardano-base/src/Cardano/Base/Bytes.hs +++ b/cardano-base/src/Cardano/Base/Bytes.hs @@ -2,6 +2,7 @@ {-# LANGUAGE TypeApplications #-} module Cardano.Base.Bytes ( + byteArrayFromByteString, byteArrayToByteString, byteStringToByteArray, byteArrayFromShortByteString, @@ -24,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/cardano-crypto-class.cabal b/cardano-crypto-class/cardano-crypto-class.cabal index bab80087d..0fe26447d 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.0.1 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/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 From b9010b02f9b8ce13ed87944101d6d3d97e2bd535 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sat, 30 May 2026 20:10:39 +0300 Subject: [PATCH 04/33] Add `psbToByteArray` --- cardano-crypto-class/CHANGELOG.md | 4 ++-- cardano-crypto-class/cardano-crypto-class.cabal | 2 +- .../src/Cardano/Crypto/PinnedSizedBytes.hs | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cardano-crypto-class/CHANGELOG.md b/cardano-crypto-class/CHANGELOG.md index a92a1e68e..c7ce15ad3 100644 --- a/cardano-crypto-class/CHANGELOG.md +++ b/cardano-crypto-class/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog for `cardano-crypto-class` -## 2.5.0.1 +## 2.5.1.0 -* +* Add `psbToByteArray` ## 2.5.0.0 diff --git a/cardano-crypto-class/cardano-crypto-class.cabal b/cardano-crypto-class/cardano-crypto-class.cabal index 0fe26447d..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.1 +version: 2.5.1.0 synopsis: Type classes abstracting over cryptography primitives for Cardano diff --git a/cardano-crypto-class/src/Cardano/Crypto/PinnedSizedBytes.hs b/cardano-crypto-class/src/Cardano/Crypto/PinnedSizedBytes.hs index 0c7e02927..0158e46a2 100644 --- a/cardano-crypto-class/src/Cardano/Crypto/PinnedSizedBytes.hs +++ b/cardano-crypto-class/src/Cardano/Crypto/PinnedSizedBytes.hs @@ -20,6 +20,7 @@ module Cardano.Crypto.PinnedSizedBytes ( -- * Conversions psbFromBytes, psbToBytes, + psbToByteArray, psbFromByteString, psbFromByteStringCheck, psbToByteString, @@ -76,6 +77,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 +188,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 From a94caf2d23ccaef55b74488db388697b9f392358 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sat, 30 May 2026 21:36:44 +0300 Subject: [PATCH 05/33] Add `psbFromByteStringM` --- cardano-crypto-class/CHANGELOG.md | 1 + .../src/Cardano/Crypto/PinnedSizedBytes.hs | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/cardano-crypto-class/CHANGELOG.md b/cardano-crypto-class/CHANGELOG.md index c7ce15ad3..d55b73f37 100644 --- a/cardano-crypto-class/CHANGELOG.md +++ b/cardano-crypto-class/CHANGELOG.md @@ -3,6 +3,7 @@ ## 2.5.1.0 * Add `psbToByteArray` +* Add `psbFromByteStringM` ## 2.5.0.0 diff --git a/cardano-crypto-class/src/Cardano/Crypto/PinnedSizedBytes.hs b/cardano-crypto-class/src/Cardano/Crypto/PinnedSizedBytes.hs index 0158e46a2..f43904e5c 100644 --- a/cardano-crypto-class/src/Cardano/Crypto/PinnedSizedBytes.hs +++ b/cardano-crypto-class/src/Cardano/Crypto/PinnedSizedBytes.hs @@ -22,6 +22,7 @@ module Cardano.Crypto.PinnedSizedBytes ( psbToBytes, psbToByteArray, psbFromByteString, + psbFromByteStringM, psbFromByteStringCheck, psbToByteString, @@ -232,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)) From a2c868dad3f46fdc0566b65c035b7e86fa9852a1 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sat, 30 May 2026 20:12:20 +0300 Subject: [PATCH 06/33] Add the odd `output` file to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) 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 From 6b5edc27b1131c79417e4d59c862e4ba2105cc88 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sat, 30 May 2026 19:13:32 +0300 Subject: [PATCH 07/33] Introduce `SecretKey` --- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 77 ++++++++++++------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 49ce85493..800fcf6bb 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -67,10 +67,12 @@ 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.TypeLits (natVal) import System.IO.Unsafe (unsafePerformIO) import Codec.CBOR.Decoding ( @@ -113,6 +115,18 @@ publicKeySize = 32 ccSize = 32 signatureSize = 64 +type SECRET_KEY_SIZE = 64 + +secretKeySize :: Int +secretKeySize = fromInteger (natVal (Proxy @SECRET_KEY_SIZE)) + +newtype SecretKey = SecretKey {unSecretKey :: MLockedSizedBytes SECRET_KEY_SIZE} + +withSecretKeyPtr :: SecretKey -> (SecretKeyPtr -> IO a) -> IO a +withSecretKeyPtr (SecretKey secretKey) action = + mlsbUseAsCPtr secretKey (action . SecretKeyPtr) +{-# INLINE withSecretKeyPtr #-} + type PublicKey = ByteString type ChainCode = ByteString type Salt = ByteString @@ -251,7 +265,7 @@ data V2Envelope = V2Envelope -- 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) + { kmSecretKey :: !SecretKey , kmPublicKey :: !PublicKey , kmChainCode :: !ChainCode } @@ -298,7 +312,7 @@ encryptedCreate sec pass cc case emat of Left err -> pure (Left err) Right mat -> - finally (wrapKeyMaterial pass mat) (mlsbFinalize (kmSecretKey mat)) + finally (wrapKeyMaterial pass mat) (mlsbFinalize (unSecretKey (kmSecretKey mat))) {-# NOINLINE encryptedCreate #-} encryptedCreateDirectWithTweak :: @@ -311,7 +325,7 @@ encryptedCreateDirectWithTweak sec pass case emat of Left err -> pure (Left err) Right mat -> - finally (wrapKeyMaterial pass mat) (mlsbFinalize (kmSecretKey mat)) + finally (wrapKeyMaterial pass mat) (mlsbFinalize (unSecretKey (kmSecretKey mat))) {-# NOINLINE encryptedCreateDirectWithTweak #-} encryptedValidatePassphrase :: @@ -322,7 +336,7 @@ encryptedValidatePassphrase ekey pass = do case emat of Left err -> pure (Left err) Right mat -> do - mlsbFinalize (kmSecretKey mat) + mlsbFinalize (unSecretKey (kmSecretKey mat)) pure (Right ()) encryptedChangePass :: @@ -334,7 +348,7 @@ encryptedChangePass oldPass newPass ekey = case emat of Left err -> pure (Left err) Right mat -> - restore (wrapKeyMaterial newPass mat) `finally` mlsbFinalize (kmSecretKey mat) + restore (wrapKeyMaterial newPass mat) `finally` mlsbFinalize (unSecretKey (kmSecretKey mat)) encryptedSign :: (ByteArrayAccess passphrase, ByteArrayAccess msg) => @@ -357,7 +371,7 @@ encryptedSign ekey pass msg = (coerce outSig) pure (if status /= 0 then Left XPrvInternalError else Right (Signature sig)) ) - `finally` mlsbFinalize (kmSecretKey mat) + `finally` mlsbFinalize (unSecretKey (kmSecretKey mat)) encryptedDerivePrivate :: ByteArrayAccess passphrase => @@ -377,9 +391,10 @@ encryptedDerivePrivate dscheme ekey pass childIndex = case echildMat of Left err -> pure (Left err) Right childMat -> - restore (wrapKeyMaterial pass childMat) `finally` mlsbFinalize (kmSecretKey childMat) + restore (wrapKeyMaterial pass childMat) + `finally` mlsbFinalize (unSecretKey (kmSecretKey childMat)) ) - `finally` mlsbFinalize (kmSecretKey parentMat) + `finally` mlsbFinalize (unSecretKey (kmSecretKey parentMat)) encryptedDerivePublic :: DerivationScheme -> @@ -429,7 +444,7 @@ encryptedChainCode (EncryptedKey ekey) = -- done with it. encryptedKeyMaterial :: ByteArrayAccess passphrase => - EncryptedKey -> passphrase -> IO (Either XPrvError (MLockedSizedBytes 64)) + EncryptedKey -> passphrase -> IO (Either XPrvError SecretKey) encryptedKeyMaterial ekey pass = do emat <- decryptKeyMaterial ekey pass case emat of @@ -598,16 +613,16 @@ v2Decrypt (EncryptedKey bs) pass = Left err -> pure (Left err) Right wrappingKey -> do let aad = encodeAad (v2PublicKey envelope) (v2ChainCode envelope) - ptextMlsb <- (mlsbNewZero :: IO (MLockedSizedBytes 64)) + secretKey <- SecretKey <$> mlsbNewZero status <- - mlsbUseAsCPtr ptextMlsb $ \ptextPtr -> + withSecretKeyPtr secretKey $ \secretKeyPtr -> withByteArray (v2Ciphertext envelope) $ \ct -> withByteArray (v2Tag envelope) $ \tg -> withByteArray aad $ \ad -> withByteArray (v2Nonce envelope) $ \np -> withByteArray wrappingKey $ \kp -> wallet_sodium_xchacha20poly1305_decrypt - (coerce ptextPtr) + secretKeyPtr (coerce ct) (fromIntegral @Int @CULLong $ BS.length (v2Ciphertext envelope)) (coerce tg) @@ -617,14 +632,14 @@ v2Decrypt (EncryptedKey bs) pass = (coerce kp) if status /= 0 then do - mlsbFinalize ptextMlsb + mlsbFinalize (unSecretKey secretKey) pure (Left XPrvAuthenticationFailed) else do - let mat = KeyMaterial ptextMlsb (v2PublicKey envelope) (v2ChainCode envelope) - eVal <- validateKeyMaterial mat `onException` mlsbFinalize ptextMlsb + let mat = KeyMaterial secretKey (v2PublicKey envelope) (v2ChainCode envelope) + eVal <- validateKeyMaterial mat `onException` mlsbFinalize (unSecretKey secretKey) case eVal of Left err -> do - mlsbFinalize ptextMlsb + mlsbFinalize (unSecretKey secretKey) pure (Left err) Right () -> pure (Right mat) @@ -646,7 +661,7 @@ wrapKeyMaterial pass material = do Left err -> pure (Left err) Right wrappingKey -> do let aad = encodeAad (kmPublicKey material) (kmChainCode material) - mlsbUseAsCPtr (kmSecretKey material) $ \skPtr -> do + withSecretKeyPtr (kmSecretKey material) $ \skPtr -> do ((status, tag), ciphertext) <- B.allocRet legacyKeySize $ \outCipher -> B.allocRet tagSize $ \outTag -> @@ -656,7 +671,7 @@ wrapKeyMaterial pass material = do wallet_sodium_xchacha20poly1305_encrypt (coerce outCipher) (coerce outTag) - (coerce skPtr) + skPtr (fromIntegral @Int @CULLong legacyKeySize) ad (fromIntegral @Int @CULLong $ BS.length aad) @@ -691,12 +706,12 @@ 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 + withSecretKeyPtr (kmSecretKey mat) $ \(SecretKeyPtr skPtr) -> + copyBytes base skPtr secretKeySize BS.useAsCStringLen (kmPublicKey mat) $ \(pkPtr, _) -> - copyBytes (base `plusPtr` 64) (castPtr pkPtr) 32 + copyBytes (base `plusPtr` secretKeySize) (castPtr pkPtr) publicKeySize BS.useAsCStringLen (kmChainCode mat) $ \(ccPtr, _) -> - copyBytes (base `plusPtr` 96) (castPtr ccPtr) 32 + copyBytes (base `plusPtr` (secretKeySize + publicKeySize)) (castPtr ccPtr) ccSize action base -- | Call a C function that writes a 128-byte @encrypted_key@ struct to the @@ -709,15 +724,21 @@ withEncryptedKeyOutput :: IO (Either XPrvError KeyMaterial) withEncryptedKeyOutput onFailure action = bracket (mlsbNewZero :: IO (MLockedSizedBytes 128)) mlsbFinalize $ \outMlsb -> do - r <- mlsbUseAsCPtr outMlsb $ \ptr -> action ptr + r <- mlsbUseAsCPtr outMlsb action 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)) + secretKey <- mlsbNewZero + mlsbUseAsCPtr secretKey $ \skPtr -> copyBytes skPtr base secretKeySize + publicKey <- BS.packCStringLen (castPtr (base `plusPtr` secretKeySize), 32) + cc <- BS.packCStringLen (castPtr (base `plusPtr` (secretKeySize + publicKeySize)), 32) + pure $ + Right $ + KeyMaterial + { kmSecretKey = SecretKey secretKey + , kmPublicKey = publicKey + , kmChainCode = cc + } -- --------------------------------------------------------------------------- -- Internal: key-material construction (using C/ed25519) From b51bc91c345164999f3e0c38d19d6340159a7833 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sat, 30 May 2026 21:37:04 +0300 Subject: [PATCH 08/33] Make `PublicKey` into a `newtype` --- .../cardano-crypto-wallet.cabal | 3 + .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 114 +++++++++++++----- .../Test/Cardano/Crypto/Wallet/SignSpec.hs | 6 +- .../Cardano/Crypto/Wallet/V2FormatSpec.hs | 73 +++++------ 4 files changed, 127 insertions(+), 69 deletions(-) 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/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 800fcf6bb..d81c75044 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -25,6 +25,11 @@ module Cardano.Crypto.WalletHD.Encrypted ( DerivationScheme (..), DerivationIndex, + -- ** PublicKey + PublicKey, + mkPublicKey, + fromPublicKey, + -- * Construction & validation encryptedCreate, encryptedCreateDirectWithTweak, @@ -51,9 +56,20 @@ module Cardano.Crypto.WalletHD.Encrypted ( withDeterministicRandomnessForTesting, ) where +import Cardano.Crypto.PinnedSizedBytes ( + PinnedSizedBytes, + psbCreate, + psbCreateResult, + psbFromByteStringCheck, + psbFromByteStringM, + psbToByteArray, + psbToByteString, + psbUseAsCPtr, + ) import Control.DeepSeq import Control.Exception (bracket, finally, mask, onException) import Control.Monad (when) +import Control.Monad.Trans.Fail.String (errorFail) import Data.Bits (shiftR) import Data.ByteArray (ByteArrayAccess, withByteArray) import qualified Data.ByteArray as B @@ -72,9 +88,17 @@ import Data.Word import Foreign.C.Types import Foreign.Marshal.Utils (copyBytes) import Foreign.Ptr +import GHC.Stack (HasCallStack) import GHC.TypeLits (natVal) import System.IO.Unsafe (unsafePerformIO) +import Cardano.Binary (toCBOR) +import Cardano.Crypto.Libsodium.MLockedBytes ( + MLockedSizedBytes, + mlsbFinalize, + mlsbNewZero, + mlsbUseAsCPtr, + ) import Codec.CBOR.Decoding ( Decoder, decodeBytes, @@ -82,6 +106,7 @@ import Codec.CBOR.Decoding ( decodeWord, ) import Codec.CBOR.Encoding ( + Encoding, encodeBytes, encodeListLen, encodeWord, @@ -89,13 +114,6 @@ import Codec.CBOR.Encoding ( 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 -- --------------------------------------------------------------------------- @@ -109,12 +127,15 @@ data DerivationScheme = DerivationScheme1 | DerivationScheme2 -- Size constants -- --------------------------------------------------------------------------- -legacyKeySize, publicKeySize, ccSize, signatureSize :: Int +legacyKeySize, ccSize, signatureSize :: Int legacyKeySize = 64 -publicKeySize = 32 ccSize = 32 signatureSize = 64 +------------------------------------------------------------------------------ +-- SECRET_KEY +------------------------------------------------------------------------------ + type SECRET_KEY_SIZE = 64 secretKeySize :: Int @@ -127,7 +148,32 @@ withSecretKeyPtr (SecretKey secretKey) action = mlsbUseAsCPtr secretKey (action . SecretKeyPtr) {-# INLINE withSecretKeyPtr #-} -type PublicKey = ByteString +------------------------------------------------------------------------------ +-- PUBLIC_KEY +------------------------------------------------------------------------------ + +type PUBLIC_KEY_SIZE = 32 + +publicKeySize :: Int +publicKeySize = fromInteger (natVal (Proxy @PUBLIC_KEY_SIZE)) + +newtype PublicKey = PublicKey {unPublicKey :: PinnedSizedBytes PUBLIC_KEY_SIZE} + deriving (Eq, Show) + +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 + +fromPublicKey :: PublicKey -> ByteString +fromPublicKey = psbToByteString . unPublicKey + +encodePublicKey :: PublicKey -> Encoding +encodePublicKey = toCBOR . psbToByteArray . unPublicKey + type ChainCode = ByteString type Salt = ByteString type Nonce = ByteString @@ -401,32 +447,32 @@ encryptedDerivePublic :: (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 -> + (newPublicKey, newCC) <- + psbCreateResult $ \publicKeyPtrOut -> B.alloc ccSize $ \outCc -> - withByteArray pub $ \ppub -> + withPublicKeyPtr publicKey $ \publicKeyPtr -> withByteArray cc $ \pcc -> do r <- wallet_encrypted_derive_public - (coerce ppub) + publicKeyPtr (coerce pcc) childIndex - (coerce outPub) + (PublicKeyPtr publicKeyPtrOut) (coerce outCc) (dschemeToC dscheme) if r /= 0 then error "encryptedDerivePublic: hardened index check failed" else pure () - pure (newPub, newCC) + pure (PublicKey newPublicKey, newCC) -encryptedPublic :: EncryptedKey -> ByteString +encryptedPublic :: HasCallStack => EncryptedKey -> PublicKey encryptedPublic (EncryptedKey ekey) = case encryptedKeyFormat (EncryptedKey ekey) of - LegacyV1 -> sub legacyKeySize publicKeySize ekey + LegacyV1 -> errorFail $ mkPublicKey $ sub legacyKeySize publicKeySize ekey EnvelopeV2 -> either (const badEnvelope) v2PublicKey (decodeV2Envelope ekey) where badEnvelope = error "encryptedPublic: invalid v2 envelope" @@ -533,7 +579,7 @@ encodeV2Envelope envelope = ] encodeAad :: PublicKey -> ChainCode -> AadContext -encodeAad pub cc = +encodeAad publicKey cc = CBOR.toStrictByteString $ mconcat [ encodeListLen 8 @@ -547,7 +593,7 @@ encodeAad pub cc = , encodeWord xchacha20poly1305Id , encodeWord 1 , encodeWord (fromIntegral legacyKeySize) - , encodeBytes pub + , encodePublicKey publicKey , encodeBytes cc ] @@ -583,11 +629,13 @@ decodeAadFields = do when (payloadKind /= 1) (failDecoder XPrvDecodeError) payloadLen <- decodeWord when (payloadLen /= fromIntegral legacyKeySize) (failDecoder XPrvInvalidCiphertextLength) - pub <- decodeBytes + pubKeyBytes <- decodeBytes cc <- decodeBytes - when (BS.length pub /= publicKeySize) (failDecoder XPrvInvalidPublicKey) + publicKey <- case psbFromByteStringCheck pubKeyBytes of + Nothing -> failDecoder XPrvInvalidPublicKey + Just pubKey -> pure $ PublicKey pubKey when (BS.length cc /= ccSize) (failDecoder XPrvInvalidChainCode) - pure (pub, cc) + pure (publicKey, cc) -- --------------------------------------------------------------------------- -- Internal: v2 encrypt / decrypt @@ -708,7 +756,7 @@ withLegacyStruct mat action = mlsbUseAsCPtr mlsb $ \base -> do withSecretKeyPtr (kmSecretKey mat) $ \(SecretKeyPtr skPtr) -> copyBytes base skPtr secretKeySize - BS.useAsCStringLen (kmPublicKey mat) $ \(pkPtr, _) -> + withPublicKeyPtr (kmPublicKey mat) $ \(PublicKeyPtr pkPtr) -> copyBytes (base `plusPtr` secretKeySize) (castPtr pkPtr) publicKeySize BS.useAsCStringLen (kmChainCode mat) $ \(ccPtr, _) -> copyBytes (base `plusPtr` (secretKeySize + publicKeySize)) (castPtr ccPtr) ccSize @@ -723,20 +771,22 @@ withEncryptedKeyOutput :: (Ptr Word8 -> IO CInt) -> IO (Either XPrvError KeyMaterial) withEncryptedKeyOutput onFailure action = - bracket (mlsbNewZero :: IO (MLockedSizedBytes 128)) mlsbFinalize $ \outMlsb -> do - r <- mlsbUseAsCPtr outMlsb action + bracket (mlsbNewZero :: IO (MLockedSizedBytes 128)) mlsbFinalize $ \tmpMlsb -> do + r <- mlsbUseAsCPtr tmpMlsb action if r /= 0 then pure (Left onFailure) - else mlsbUseAsCPtr outMlsb $ \base -> do + else mlsbUseAsCPtr tmpMlsb $ \tmpPtr -> do secretKey <- mlsbNewZero - mlsbUseAsCPtr secretKey $ \skPtr -> copyBytes skPtr base secretKeySize - publicKey <- BS.packCStringLen (castPtr (base `plusPtr` secretKeySize), 32) - cc <- BS.packCStringLen (castPtr (base `plusPtr` (secretKeySize + publicKeySize)), 32) + mlsbUseAsCPtr secretKey $ \skPtr -> copyBytes skPtr tmpPtr secretKeySize + publicKey <- + psbCreate $ \pkPtr -> + copyBytes pkPtr (tmpPtr `plusPtr` secretKeySize) publicKeySize + cc <- BS.packCStringLen (castPtr (tmpPtr `plusPtr` (secretKeySize + publicKeySize)), 32) pure $ Right $ KeyMaterial { kmSecretKey = SecretKey secretKey - , kmPublicKey = publicKey + , kmPublicKey = PublicKey publicKey , kmChainCode = cc } 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 356188dfe..744b5af9c 100644 --- a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/SignSpec.hs +++ b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/SignSpec.hs @@ -19,8 +19,8 @@ foreign import ccall "cardano_crypto_ed25519_sign_open" 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 sig $ \sigp -> @@ -30,6 +30,8 @@ verifySignature pub msg (Signature sig) = unsafePerformIO $ (fromIntegral @Int @CSize ml) (castPtr pkp) (castPtr sigp) + where + pub = fromPublicKey publicKey testSeed :: BS.ByteString testSeed = BS.replicate 32 0x02 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 8d6030b10..523e68a57 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 @@ -28,42 +29,44 @@ wrongPass = BS.replicate 32 0x00 -- 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 From 2c72a019b892acc8f5cc276f2a5ef34f32e2b644 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 09:06:22 +0300 Subject: [PATCH 09/33] Introduce `withDecryptedKeyMaterial` This properly fixes scoping and async exception handling for decrypting `KeyMaterial` Also rename `encryptedChangePass` -> `encryptedChangePassphrase` for consistency with `encryptedValidatePassphrase` --- cardano-crypto-wallet/bench/Main.hs | 2 +- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 130 +++++++----------- .../Cardano/Crypto/Wallet/RoundTripSpec.hs | 10 +- .../Test/Cardano/Crypto/Wallet/SignSpec.hs | 8 +- .../Cardano/Crypto/Wallet/V2FormatSpec.hs | 4 +- 5 files changed, 62 insertions(+), 92 deletions(-) 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/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index d81c75044..b415ce1ee 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE TypeApplications #-} -- | @@ -39,7 +40,7 @@ module Cardano.Crypto.WalletHD.Encrypted ( -- * Passphrase operations encryptedValidatePassphrase, - encryptedChangePass, + encryptedChangePassphrase, -- * Signing & derivation encryptedSign, @@ -49,7 +50,6 @@ module Cardano.Crypto.WalletHD.Encrypted ( -- * Accessors encryptedPublic, encryptedChainCode, - encryptedKeyMaterial, -- * Test helpers withFastKdfForTesting, @@ -67,7 +67,7 @@ import Cardano.Crypto.PinnedSizedBytes ( psbUseAsCPtr, ) import Control.DeepSeq -import Control.Exception (bracket, finally, mask, onException) +import Control.Exception (bracket, finally) import Control.Monad (when) import Control.Monad.Trans.Fail.String (errorFail) import Data.Bits (shiftR) @@ -148,6 +148,9 @@ withSecretKeyPtr (SecretKey secretKey) action = mlsbUseAsCPtr secretKey (action . SecretKeyPtr) {-# INLINE withSecretKeyPtr #-} +finalizeSecretKey :: SecretKey -> IO () +finalizeSecretKey = mlsbFinalize . unSecretKey + ------------------------------------------------------------------------------ -- PUBLIC_KEY ------------------------------------------------------------------------------ @@ -308,14 +311,15 @@ data V2Envelope = V2Envelope deriving (Eq, Show) -- | 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 :: !SecretKey , kmPublicKey :: !PublicKey , kmChainCode :: !ChainCode } +finalizeKeyMaterial :: KeyMaterial -> IO () +finalizeKeyMaterial = finalizeSecretKey . kmSecretKey + -- FFI pointer newtypes newtype SecretKeyPtr = SecretKeyPtr (Ptr Word8) newtype MasterKeyPtr = MasterKeyPtr (Ptr Word8) @@ -377,47 +381,30 @@ encryptedCreateDirectWithTweak sec 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 (unSecretKey (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 (unSecretKey (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 (unSecretKey (kmSecretKey mat)) +encryptedSign eKey pass msg = + withDecryptedKeyMaterial eKey pass $ \keyMaterial -> + withLegacyStruct keyMaterial $ \legacyStructPtr -> do + (status, sig) <- + B.allocRet signatureSize $ \outSig -> + withByteArray msg $ \msgPtr -> + wallet_encrypted_sign + (coerce legacyStructPtr) + msgPtr + (fromIntegral $ B.length msg) + (coerce outSig) + pure (if status /= 0 then Left XPrvInternalError else Right (Signature sig)) encryptedDerivePrivate :: ByteArrayAccess passphrase => @@ -426,21 +413,14 @@ encryptedDerivePrivate :: passphrase -> DerivationIndex -> IO (Either XPrvError EncryptedKey) -encryptedDerivePrivate dscheme ekey pass childIndex = - mask $ \restore -> do - emat <- restore (decryptKeyMaterial ekey pass) - case emat of +encryptedDerivePrivate dScheme eKey pass childIndex = + withDecryptedKeyMaterial eKey pass $ \parentKeyMaterial -> do + echildMat <- legacyDerivePrivate dScheme parentKeyMaterial childIndex + case echildMat 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 (unSecretKey (kmSecretKey childMat)) - ) - `finally` mlsbFinalize (unSecretKey (kmSecretKey parentMat)) + Right childMat -> + wrapKeyMaterial pass childMat + `finally` mlsbFinalize (unSecretKey (kmSecretKey childMat)) encryptedDerivePublic :: DerivationScheme -> @@ -485,18 +465,6 @@ encryptedChainCode (EncryptedKey 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 SecretKey) -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 -- --------------------------------------------------------------------------- @@ -641,18 +609,26 @@ decodeAadFields = do -- Internal: v2 encrypt / decrypt -- --------------------------------------------------------------------------- -decryptKeyMaterial :: +withDecryptedKeyMaterial :: ByteArrayAccess passphrase => - EncryptedKey -> passphrase -> IO (Either XPrvError KeyMaterial) -decryptKeyMaterial ekey pass = + EncryptedKey -> passphrase -> (KeyMaterial -> 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 -> + bracket (decryptKeyMaterialV2 ekey pass) (mapM_ finalizeKeyMaterial) $ \case + Left err -> pure $ Left err + Right keyMaterial -> + validateKeyMaterial keyMaterial >>= \case + Left err -> pure $ Left err + Right () -> action keyMaterial + +-- | This function is unsafe and should not be exported. Whenver used it must have async exceptions +-- masked and resulting `KeyMaterial` must be finalized after the result served its use. +decryptKeyMaterialV2 :: ByteArrayAccess passphrase => EncryptedKey -> passphrase -> IO (Either XPrvError KeyMaterial) -v2Decrypt (EncryptedKey bs) pass = +decryptKeyMaterialV2 (EncryptedKey bs) pass = case decodeV2Envelope bs of Left err -> pure (Left err) Right envelope -> do @@ -681,15 +657,9 @@ v2Decrypt (EncryptedKey bs) pass = if status /= 0 then do mlsbFinalize (unSecretKey secretKey) - pure (Left XPrvAuthenticationFailed) + pure $ Left XPrvAuthenticationFailed else do - let mat = KeyMaterial secretKey (v2PublicKey envelope) (v2ChainCode envelope) - eVal <- validateKeyMaterial mat `onException` mlsbFinalize (unSecretKey secretKey) - case eVal of - Left err -> do - mlsbFinalize (unSecretKey secretKey) - pure (Left err) - Right () -> pure (Right mat) + pure $ Right $ KeyMaterial secretKey (v2PublicKey envelope) (v2ChainCode envelope) wrapKeyMaterial :: ByteArrayAccess passphrase => 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..ae4c08707 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'') 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 744b5af9c..b9a0324b5 100644 --- a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/SignSpec.hs +++ b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/SignSpec.hs @@ -81,9 +81,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 @@ -100,8 +100,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 523e68a57..43a19a350 100644 --- a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/V2FormatSpec.hs +++ b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/V2FormatSpec.hs @@ -134,12 +134,12 @@ tests = describe "V2Format" $ do encryptedKey (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 From 7165b0a28885e6d7cc272e0e244d599ac3d8de37 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 09:28:09 +0300 Subject: [PATCH 10/33] Introduce proper scoping in `withEncryptedKeyOutput` --- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 75 +++++++++---------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index b415ce1ee..75f1c4bfe 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -67,7 +67,7 @@ import Cardano.Crypto.PinnedSizedBytes ( psbUseAsCPtr, ) import Control.DeepSeq -import Control.Exception (bracket, finally) +import Control.Exception (bracket) import Control.Monad (when) import Control.Monad.Trans.Fail.String (errorFail) import Data.Bits (shiftR) @@ -357,12 +357,7 @@ encryptedCreate :: 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 (unSecretKey (kmSecretKey mat))) + | otherwise = legacyMaterialFromSecret sec cc (wrapKeyMaterial pass) {-# NOINLINE encryptedCreate #-} encryptedCreateDirectWithTweak :: @@ -370,12 +365,7 @@ encryptedCreateDirectWithTweak :: 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 (unSecretKey (kmSecretKey mat))) + | otherwise = legacyMaterialFromMasterKey sec (wrapKeyMaterial pass) {-# NOINLINE encryptedCreateDirectWithTweak #-} encryptedValidatePassphrase :: @@ -414,13 +404,8 @@ encryptedDerivePrivate :: DerivationIndex -> IO (Either XPrvError EncryptedKey) encryptedDerivePrivate dScheme eKey pass childIndex = - withDecryptedKeyMaterial eKey pass $ \parentKeyMaterial -> do - echildMat <- legacyDerivePrivate dScheme parentKeyMaterial childIndex - case echildMat of - Left err -> pure (Left err) - Right childMat -> - wrapKeyMaterial pass childMat - `finally` mlsbFinalize (unSecretKey (kmSecretKey childMat)) + withDecryptedKeyMaterial eKey pass $ \parentKeyMaterial -> + legacyDerivePrivate dScheme parentKeyMaterial childIndex (wrapKeyMaterial pass) encryptedDerivePublic :: DerivationScheme -> @@ -738,22 +723,22 @@ withLegacyStruct mat action = -- 'MLockedSizedBytes 64' in the returned 'KeyMaterial' and must finalize it. withEncryptedKeyOutput :: XPrvError -> + (KeyMaterial -> IO (Either XPrvError a)) -> (Ptr Word8 -> IO CInt) -> - IO (Either XPrvError KeyMaterial) -withEncryptedKeyOutput onFailure action = + IO (Either XPrvError a) +withEncryptedKeyOutput onFailure keyMaterialAction structPtrAction = bracket (mlsbNewZero :: IO (MLockedSizedBytes 128)) mlsbFinalize $ \tmpMlsb -> do - r <- mlsbUseAsCPtr tmpMlsb action + r <- mlsbUseAsCPtr tmpMlsb structPtrAction if r /= 0 then pure (Left onFailure) else mlsbUseAsCPtr tmpMlsb $ \tmpPtr -> do - secretKey <- mlsbNewZero - mlsbUseAsCPtr secretKey $ \skPtr -> copyBytes skPtr tmpPtr secretKeySize - publicKey <- - psbCreate $ \pkPtr -> - copyBytes pkPtr (tmpPtr `plusPtr` secretKeySize) publicKeySize - cc <- BS.packCStringLen (castPtr (tmpPtr `plusPtr` (secretKeySize + publicKeySize)), 32) - pure $ - Right $ + bracket mlsbNewZero mlsbFinalize $ \secretKey -> do + mlsbUseAsCPtr secretKey $ \skPtr -> copyBytes skPtr tmpPtr secretKeySize + publicKey <- + psbCreate $ \pkPtr -> + copyBytes pkPtr (tmpPtr `plusPtr` secretKeySize) publicKeySize + cc <- BS.packCStringLen (castPtr (tmpPtr `plusPtr` (secretKeySize + publicKeySize)), 32) + keyMaterialAction $ KeyMaterial { kmSecretKey = SecretKey secretKey , kmPublicKey = PublicKey publicKey @@ -766,25 +751,35 @@ withEncryptedKeyOutput onFailure action = legacyMaterialFromSecret :: (ByteArrayAccess secret, ByteArrayAccess cc) => - secret -> cc -> IO (Either XPrvError KeyMaterial) -legacyMaterialFromSecret sec cc = - withEncryptedKeyOutput XPrvInvalidSecretKey $ \outPtr -> + secret -> + cc -> + (KeyMaterial -> IO (Either XPrvError a)) -> + IO (Either XPrvError a) +legacyMaterialFromSecret sec cc action = + withEncryptedKeyOutput XPrvInvalidSecretKey action $ \outPtr -> withByteArray sec $ \psec -> withByteArray cc $ \pcc -> wallet_encrypted_from_secret (coerce psec) (coerce pcc) (coerce outPtr) legacyMaterialFromMasterKey :: - ByteArrayAccess secret => secret -> IO (Either XPrvError KeyMaterial) -legacyMaterialFromMasterKey sec = - withEncryptedKeyOutput XPrvInvalidSecretKey $ \outPtr -> + ByteArrayAccess secret => + secret -> + (KeyMaterial -> IO (Either XPrvError a)) -> + IO (Either XPrvError a) +legacyMaterialFromMasterKey sec action = + withEncryptedKeyOutput XPrvInvalidSecretKey action $ \outPtr -> withByteArray sec $ \psec -> wallet_encrypted_new_from_mkg (MasterKeyPtr psec) (coerce outPtr) legacyDerivePrivate :: - DerivationScheme -> KeyMaterial -> DerivationIndex -> IO (Either XPrvError KeyMaterial) -legacyDerivePrivate dscheme parent childIndex = + DerivationScheme -> + KeyMaterial -> + DerivationIndex -> + (KeyMaterial -> IO (Either XPrvError a)) -> + IO (Either XPrvError a) +legacyDerivePrivate dscheme parent childIndex action = withLegacyStruct parent $ \inPtr -> - withEncryptedKeyOutput XPrvInternalError $ \outPtr -> + withEncryptedKeyOutput XPrvInternalError action $ \outPtr -> wallet_encrypted_derive_private (coerce inPtr) childIndex From 0fa54e962c4a9b2dc441c72ff9dd548abf3434bd Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 10:00:11 +0300 Subject: [PATCH 11/33] Introduce `Validity` parameter to `KeyMaterial` --- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 143 ++++++++++-------- 1 file changed, 80 insertions(+), 63 deletions(-) diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 75f1c4bfe..9fcff5310 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -1,7 +1,10 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeData #-} -- | -- Module : Cardano.Crypto.WalletHD.Encrypted @@ -310,14 +313,16 @@ data V2Envelope = V2Envelope } deriving (Eq, Show) +type data Validity = Validated | Unchecked + -- | Key material with the secret key in @sodium_malloc@'d locked memory. -data KeyMaterial = KeyMaterial +data KeyMaterial (v :: Validity) = KeyMaterial { kmSecretKey :: !SecretKey , kmPublicKey :: !PublicKey , kmChainCode :: !ChainCode } -finalizeKeyMaterial :: KeyMaterial -> IO () +finalizeKeyMaterial :: KeyMaterial v -> IO () finalizeKeyMaterial = finalizeSecretKey . kmSecretKey -- FFI pointer newtypes @@ -596,23 +601,26 @@ decodeAadFields = do withDecryptedKeyMaterial :: ByteArrayAccess passphrase => - EncryptedKey -> passphrase -> (KeyMaterial -> IO (Either XPrvError a)) -> IO (Either XPrvError a) + 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 -> bracket (decryptKeyMaterialV2 ekey pass) (mapM_ finalizeKeyMaterial) $ \case Left err -> pure $ Left err - Right keyMaterial -> - validateKeyMaterial keyMaterial >>= \case + Right uncheckedKeyMaterial -> + validateKeyMaterial uncheckedKeyMaterial >>= \case Left err -> pure $ Left err - Right () -> action keyMaterial + Right keyMaterial -> action keyMaterial -- | This function is unsafe and should not be exported. Whenver used it must have async exceptions -- masked and resulting `KeyMaterial` must be finalized after the result served its use. decryptKeyMaterialV2 :: ByteArrayAccess passphrase => - EncryptedKey -> passphrase -> IO (Either XPrvError KeyMaterial) + EncryptedKey -> passphrase -> IO (Either XPrvError (KeyMaterial Unchecked)) decryptKeyMaterialV2 (EncryptedKey bs) pass = case decodeV2Envelope bs of Left err -> pure (Left err) @@ -643,60 +651,65 @@ decryptKeyMaterialV2 (EncryptedKey bs) pass = then do mlsbFinalize (unSecretKey secretKey) pure $ Left XPrvAuthenticationFailed - else do - pure $ Right $ KeyMaterial secretKey (v2PublicKey envelope) (v2ChainCode envelope) + else + pure $ + Right $ + KeyMaterial + { kmSecretKey = secretKey + , kmPublicKey = v2PublicKey envelope + , kmChainCode = v2ChainCode envelope + } wrapKeyMaterial :: ByteArrayAccess passphrase => - passphrase -> KeyMaterial -> IO (Either XPrvError EncryptedKey) + passphrase -> KeyMaterial Validated -> IO (Either XPrvError EncryptedKey) wrapKeyMaterial pass material = do - eVal <- validateKeyMaterial material - case eVal of + eSalt <- randomBytesIO saltSize + eNonce <- randomBytesIO nonceSize + case (,) <$> eSalt <*> eNonce of Left err -> pure (Left err) - Right () -> do - eSalt <- randomBytesIO saltSize - eNonce <- randomBytesIO nonceSize - case (,) <$> eSalt <*> eNonce of + Right (salt, nonce) -> do + eWrappingKey <- deriveWrappingKey pass salt + case eWrappingKey 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) - withSecretKeyPtr (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) - 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 -> + Right wrappingKey -> do + let aad = encodeAad (kmPublicKey material) (kmChainCode material) + withSecretKeyPtr (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) + 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 Unchecked -> IO (Either XPrvError (KeyMaterial Validated)) +validateKeyMaterial keyMaterial@KeyMaterial {..} = + withLegacyStruct keyMaterial $ \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 ()) + pure $ + if r /= 0 + then Left XPrvPublicKeyMismatch + else Right (KeyMaterial {..}) -- --------------------------------------------------------------------------- -- Internal: locked memory helpers @@ -705,7 +718,7 @@ 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 :: KeyMaterial v -> (Ptr Word8 -> IO r) -> IO r withLegacyStruct mat action = bracket (mlsbNewZero :: IO (MLockedSizedBytes 128)) mlsbFinalize $ \mlsb -> mlsbUseAsCPtr mlsb $ \base -> do @@ -723,7 +736,7 @@ withLegacyStruct mat action = -- 'MLockedSizedBytes 64' in the returned 'KeyMaterial' and must finalize it. withEncryptedKeyOutput :: XPrvError -> - (KeyMaterial -> IO (Either XPrvError a)) -> + (KeyMaterial Validated -> IO (Either XPrvError a)) -> (Ptr Word8 -> IO CInt) -> IO (Either XPrvError a) withEncryptedKeyOutput onFailure keyMaterialAction structPtrAction = @@ -738,12 +751,16 @@ withEncryptedKeyOutput onFailure keyMaterialAction structPtrAction = psbCreate $ \pkPtr -> copyBytes pkPtr (tmpPtr `plusPtr` secretKeySize) publicKeySize cc <- BS.packCStringLen (castPtr (tmpPtr `plusPtr` (secretKeySize + publicKeySize)), 32) - keyMaterialAction $ - KeyMaterial - { kmSecretKey = SecretKey secretKey - , kmPublicKey = PublicKey publicKey - , kmChainCode = cc - } + eKeyMaterial <- + validateKeyMaterial $ + KeyMaterial + { kmSecretKey = SecretKey secretKey + , kmPublicKey = PublicKey publicKey + , kmChainCode = cc + } + case eKeyMaterial of + Left err -> pure $ Left err + Right keyMaterial -> keyMaterialAction keyMaterial -- --------------------------------------------------------------------------- -- Internal: key-material construction (using C/ed25519) @@ -753,7 +770,7 @@ legacyMaterialFromSecret :: (ByteArrayAccess secret, ByteArrayAccess cc) => secret -> cc -> - (KeyMaterial -> IO (Either XPrvError a)) -> + (KeyMaterial Validated -> IO (Either XPrvError a)) -> IO (Either XPrvError a) legacyMaterialFromSecret sec cc action = withEncryptedKeyOutput XPrvInvalidSecretKey action $ \outPtr -> @@ -764,7 +781,7 @@ legacyMaterialFromSecret sec cc action = legacyMaterialFromMasterKey :: ByteArrayAccess secret => secret -> - (KeyMaterial -> IO (Either XPrvError a)) -> + (KeyMaterial Validated -> IO (Either XPrvError a)) -> IO (Either XPrvError a) legacyMaterialFromMasterKey sec action = withEncryptedKeyOutput XPrvInvalidSecretKey action $ \outPtr -> @@ -773,9 +790,9 @@ legacyMaterialFromMasterKey sec action = legacyDerivePrivate :: DerivationScheme -> - KeyMaterial -> + KeyMaterial Validated -> DerivationIndex -> - (KeyMaterial -> IO (Either XPrvError a)) -> + (KeyMaterial Validated -> IO (Either XPrvError a)) -> IO (Either XPrvError a) legacyDerivePrivate dscheme parent childIndex action = withLegacyStruct parent $ \inPtr -> From 1f55856a2ea230d1026109e69350ed56653d01f4 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 10:22:29 +0300 Subject: [PATCH 12/33] Improve `EncryptedKey` type safety Also introduce a more descriptive `mkEncryptedKey` function that deprecates `encryptedKey` --- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 52 +++++++++++-------- .../Cardano/Crypto/Wallet/RoundTripSpec.hs | 4 +- .../Cardano/Crypto/Wallet/V2FormatSpec.hs | 8 +-- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 9fcff5310..a22bbd892 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -37,6 +37,7 @@ module Cardano.Crypto.WalletHD.Encrypted ( -- * Construction & validation encryptedCreate, encryptedCreateDirectWithTweak, + mkEncryptedKey, encryptedKey, unEncryptedKey, encryptedKeyFormat, @@ -345,8 +346,22 @@ 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 -> () <$ decodeV2Envelope eKey encryptedKeyFormat :: EncryptedKey -> XPrvFormat encryptedKeyFormat (EncryptedKey bs) @@ -440,37 +455,28 @@ encryptedDerivePublic dscheme (publicKey, cc) childIndex pure (PublicKey newPublicKey, newCC) encryptedPublic :: HasCallStack => EncryptedKey -> PublicKey -encryptedPublic (EncryptedKey ekey) = - case encryptedKeyFormat (EncryptedKey ekey) of - LegacyV1 -> errorFail $ mkPublicKey $ sub legacyKeySize publicKeySize ekey - EnvelopeV2 -> either (const badEnvelope) v2PublicKey (decodeV2Envelope ekey) +encryptedPublic eKey@(EncryptedKey eKeyBytes) = + case encryptedKeyFormat eKey of + LegacyV1 -> errorFail $ mkPublicKey $ sub legacyKeySize publicKeySize eKeyBytes + EnvelopeV2 -> either (const badEnvelope) v2PublicKey (decodeV2Envelope 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 eKey@(EncryptedKey eKeyBytes) = + case encryptedKeyFormat eKey of + LegacyV1 -> sub (legacyKeySize + publicKeySize) ccSize eKeyBytes + EnvelopeV2 -> either (const badEnvelope) v2ChainCode (decodeV2Envelope eKey) where badEnvelope = error "encryptedChainCode: invalid v2 envelope" --- --------------------------------------------------------------------------- --- 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 +decodeV2Envelope :: EncryptedKey -> Either XPrvError V2Envelope +decodeV2Envelope (EncryptedKey eKeyBytes) = + case CBOR.deserialiseFromBytes decodeEnvelope (BL.fromStrict eKeyBytes) of Right (rest, envelope) | BL.null rest -> Right envelope _ -> Left XPrvDecodeError @@ -621,8 +627,8 @@ withDecryptedKeyMaterial ekey pass action = decryptKeyMaterialV2 :: ByteArrayAccess passphrase => EncryptedKey -> passphrase -> IO (Either XPrvError (KeyMaterial Unchecked)) -decryptKeyMaterialV2 (EncryptedKey bs) pass = - case decodeV2Envelope bs of +decryptKeyMaterialV2 eKey pass = + case decodeV2Envelope eKey of Left err -> pure (Left err) Right envelope -> do eWrappingKey <- deriveWrappingKey pass (v2Salt envelope) 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 ae4c08707..6e4cdf93a 100644 --- a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/RoundTripSpec.hs +++ b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/RoundTripSpec.hs @@ -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/V2FormatSpec.hs b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/V2FormatSpec.hs index 43a19a350..1de74e7b2 100644 --- a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/V2FormatSpec.hs +++ b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/V2FormatSpec.hs @@ -112,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 @@ -120,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 @@ -131,7 +131,7 @@ 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 "encryptedChangePassphrase re-randomizes envelope (different bytes, same public key)" $ do From dc49f212cfd51ceeb8a9ded72baddc2040a1fa80 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 10:47:56 +0300 Subject: [PATCH 13/33] Make `ChainCode` into a `newtype` --- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 102 ++++++++++++------ .../Test/Cardano/Crypto/Wallet/SignSpec.hs | 4 +- 2 files changed, 73 insertions(+), 33 deletions(-) diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index a22bbd892..a8700e1b9 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -2,6 +2,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeData #-} @@ -32,7 +33,14 @@ module Cardano.Crypto.WalletHD.Encrypted ( -- ** PublicKey PublicKey, mkPublicKey, - fromPublicKey, + publicKeyByteArray, + publicKeyByteString, + + -- ** ChainCode + ChainCode, + mkChainCode, + chainCodeByteArray, + chainCodeByteString, -- * Construction & validation encryptedCreate, @@ -64,7 +72,6 @@ import Cardano.Crypto.PinnedSizedBytes ( PinnedSizedBytes, psbCreate, psbCreateResult, - psbFromByteStringCheck, psbFromByteStringM, psbToByteArray, psbToByteString, @@ -74,6 +81,7 @@ import Control.DeepSeq 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 @@ -131,9 +139,8 @@ data DerivationScheme = DerivationScheme1 | DerivationScheme2 -- Size constants -- --------------------------------------------------------------------------- -legacyKeySize, ccSize, signatureSize :: Int +legacyKeySize, signatureSize :: Int legacyKeySize = 64 -ccSize = 32 signatureSize = 64 ------------------------------------------------------------------------------ @@ -175,13 +182,44 @@ withPublicKeyPtr (PublicKey publicKey) action = mkPublicKey :: MonadFail f => ByteString -> f PublicKey mkPublicKey bs = PublicKey <$> psbFromByteStringM bs -fromPublicKey :: PublicKey -> ByteString -fromPublicKey = psbToByteString . unPublicKey +publicKeyByteArray :: PublicKey -> ByteArray +publicKeyByteArray = psbToByteArray . unPublicKey + +publicKeyByteString :: PublicKey -> ByteString +publicKeyByteString = psbToByteString . unPublicKey encodePublicKey :: PublicKey -> Encoding encodePublicKey = toCBOR . psbToByteArray . unPublicKey -type ChainCode = ByteString +------------------------------------------------------------------------------ +-- 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) + +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 + type Salt = ByteString type Nonce = ByteString type Ciphertext = ByteString @@ -189,7 +227,7 @@ type AuthenticationTag = ByteString type AadContext = ByteString legacyTotalKeySize :: Int -legacyTotalKeySize = legacyKeySize + publicKeySize + ccSize +legacyTotalKeySize = legacyKeySize + publicKeySize + chainCodeSize -- --------------------------------------------------------------------------- -- V2 envelope constants @@ -376,7 +414,7 @@ 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) + | B.length cc /= chainCodeSize = pure (Left XPrvInvalidChainCode) | otherwise = legacyMaterialFromSecret sec cc (wrapKeyMaterial pass) {-# NOINLINE encryptedCreate #-} @@ -436,23 +474,23 @@ encryptedDerivePublic dscheme (publicKey, cc) childIndex | childIndex >= 0x80000000 = error "encryptedDerivePublic: cannot derive hardened key from public key" | otherwise = unsafePerformIO $ do - (newPublicKey, newCC) <- + (newPublicKey, newChainCode) <- psbCreateResult $ \publicKeyPtrOut -> - B.alloc ccSize $ \outCc -> + psbCreate $ \ccOutPtr -> withPublicKeyPtr publicKey $ \publicKeyPtr -> - withByteArray cc $ \pcc -> do + withChainCodePtr cc $ \chainCodePtr -> do r <- wallet_encrypted_derive_public publicKeyPtr - (coerce pcc) + chainCodePtr childIndex (PublicKeyPtr publicKeyPtrOut) - (coerce outCc) + (ChainCodePtr ccOutPtr) (dschemeToC dscheme) if r /= 0 then error "encryptedDerivePublic: hardened index check failed" else pure () - pure (PublicKey newPublicKey, newCC) + pure (PublicKey newPublicKey, ChainCode newChainCode) encryptedPublic :: HasCallStack => EncryptedKey -> PublicKey encryptedPublic eKey@(EncryptedKey eKeyBytes) = @@ -462,10 +500,11 @@ encryptedPublic eKey@(EncryptedKey eKeyBytes) = where badEnvelope = error "encryptedPublic: invalid v2 envelope" -encryptedChainCode :: EncryptedKey -> ByteString +encryptedChainCode :: HasCallStack => EncryptedKey -> ChainCode encryptedChainCode eKey@(EncryptedKey eKeyBytes) = case encryptedKeyFormat eKey of - LegacyV1 -> sub (legacyKeySize + publicKeySize) ccSize eKeyBytes + LegacyV1 -> + errorFail $ mkChainCode $ sub (legacyKeySize + publicKeySize) chainCodeSize eKeyBytes EnvelopeV2 -> either (const badEnvelope) v2ChainCode (decodeV2Envelope eKey) where badEnvelope = error "encryptedChainCode: invalid v2 envelope" @@ -558,7 +597,7 @@ encodeAad publicKey cc = , encodeWord 1 , encodeWord (fromIntegral legacyKeySize) , encodePublicKey publicKey - , encodeBytes cc + , encodeChainCode cc ] decodeAad :: AadContext -> Either XPrvError (PublicKey, ChainCode) @@ -594,12 +633,13 @@ decodeAadFields = do payloadLen <- decodeWord when (payloadLen /= fromIntegral legacyKeySize) (failDecoder XPrvInvalidCiphertextLength) pubKeyBytes <- decodeBytes - cc <- decodeBytes - publicKey <- case psbFromByteStringCheck pubKeyBytes of + chainCodeBytes <- decodeBytes + case mkPublicKey pubKeyBytes of Nothing -> failDecoder XPrvInvalidPublicKey - Just pubKey -> pure $ PublicKey pubKey - when (BS.length cc /= ccSize) (failDecoder XPrvInvalidChainCode) - pure (publicKey, cc) + Just publicKey -> + case mkChainCode chainCodeBytes of + Nothing -> failDecoder XPrvInvalidChainCode + Just chainCode -> pure (publicKey, chainCode) -- --------------------------------------------------------------------------- -- Internal: v2 encrypt / decrypt @@ -725,15 +765,15 @@ validateKeyMaterial keyMaterial@KeyMaterial {..} = -- '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 v -> (Ptr Word8 -> IO r) -> IO r -withLegacyStruct mat action = +withLegacyStruct KeyMaterial {kmSecretKey, kmPublicKey, kmChainCode} action = bracket (mlsbNewZero :: IO (MLockedSizedBytes 128)) mlsbFinalize $ \mlsb -> mlsbUseAsCPtr mlsb $ \base -> do - withSecretKeyPtr (kmSecretKey mat) $ \(SecretKeyPtr skPtr) -> + withSecretKeyPtr kmSecretKey $ \(SecretKeyPtr skPtr) -> copyBytes base skPtr secretKeySize - withPublicKeyPtr (kmPublicKey mat) $ \(PublicKeyPtr pkPtr) -> + withPublicKeyPtr kmPublicKey $ \(PublicKeyPtr pkPtr) -> copyBytes (base `plusPtr` secretKeySize) (castPtr pkPtr) publicKeySize - BS.useAsCStringLen (kmChainCode mat) $ \(ccPtr, _) -> - copyBytes (base `plusPtr` (secretKeySize + publicKeySize)) (castPtr ccPtr) ccSize + withChainCodePtr kmChainCode $ \(ChainCodePtr ccPtr) -> + copyBytes (base `plusPtr` (secretKeySize + publicKeySize)) (castPtr ccPtr) chainCodeSize action base -- | Call a C function that writes a 128-byte @encrypted_key@ struct to the @@ -756,13 +796,15 @@ withEncryptedKeyOutput onFailure keyMaterialAction structPtrAction = publicKey <- psbCreate $ \pkPtr -> copyBytes pkPtr (tmpPtr `plusPtr` secretKeySize) publicKeySize - cc <- BS.packCStringLen (castPtr (tmpPtr `plusPtr` (secretKeySize + publicKeySize)), 32) + chainCode <- + psbCreate $ \ccPtr -> + copyBytes ccPtr (tmpPtr `plusPtr` (secretKeySize + publicKeySize)) chainCodeSize eKeyMaterial <- validateKeyMaterial $ KeyMaterial { kmSecretKey = SecretKey secretKey , kmPublicKey = PublicKey publicKey - , kmChainCode = cc + , kmChainCode = ChainCode chainCode } case eKeyMaterial of Left err -> pure $ Left err 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 b9a0324b5..91467c2ea 100644 --- a/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/SignSpec.hs +++ b/cardano-crypto-wallet/test/Test/Cardano/Crypto/Wallet/SignSpec.hs @@ -22,7 +22,7 @@ foreign import ccall "cardano_crypto_ed25519_sign_open" 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 @@ -30,8 +30,6 @@ verifySignature publicKey msg (Signature sig) = unsafePerformIO $ (fromIntegral @Int @CSize ml) (castPtr pkp) (castPtr sigp) - where - pub = fromPublicKey publicKey testSeed :: BS.ByteString testSeed = BS.replicate 32 0x02 From 0e52faa2279fff180acabf51fd5d43db2540e049 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 11:15:01 +0300 Subject: [PATCH 14/33] Improve scoping of `decryptKeyMaterialV2` Extracting `SecretKey` construction outside, makes resource allocation in `bracket` much cheaper, which is absolutely what we want for proper async exception handling --- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index a8700e1b9..0f8453c41 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -361,9 +361,6 @@ data KeyMaterial (v :: Validity) = KeyMaterial , kmChainCode :: !ChainCode } -finalizeKeyMaterial :: KeyMaterial v -> IO () -finalizeKeyMaterial = finalizeSecretKey . kmSecretKey - -- FFI pointer newtypes newtype SecretKeyPtr = SecretKeyPtr (Ptr Word8) newtype MasterKeyPtr = MasterKeyPtr (Ptr Word8) @@ -655,19 +652,22 @@ withDecryptedKeyMaterial ekey pass action = case encryptedKeyFormat ekey of LegacyV1 -> pure (Left XPrvDecodeError) EnvelopeV2 -> - bracket (decryptKeyMaterialV2 ekey pass) (mapM_ finalizeKeyMaterial) $ \case - Left err -> pure $ Left err - Right uncheckedKeyMaterial -> - validateKeyMaterial uncheckedKeyMaterial >>= \case - Left err -> pure $ Left err - Right keyMaterial -> action keyMaterial + bracket (SecretKey <$> mlsbNewZero) finalizeSecretKey $ \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 --- | This function is unsafe and should not be exported. Whenver used it must have async exceptions --- masked and resulting `KeyMaterial` must be finalized after the result served its use. decryptKeyMaterialV2 :: ByteArrayAccess passphrase => - EncryptedKey -> passphrase -> IO (Either XPrvError (KeyMaterial Unchecked)) -decryptKeyMaterialV2 eKey pass = + -- | SecretKey that will be populated from EncryptedKey + SecretKey -> + EncryptedKey -> + passphrase -> + IO (Either XPrvError (KeyMaterial Unchecked)) +decryptKeyMaterialV2 secretKey eKey pass = case decodeV2Envelope eKey of Left err -> pure (Left err) Right envelope -> do @@ -676,7 +676,6 @@ decryptKeyMaterialV2 eKey pass = Left err -> pure (Left err) Right wrappingKey -> do let aad = encodeAad (v2PublicKey envelope) (v2ChainCode envelope) - secretKey <- SecretKey <$> mlsbNewZero status <- withSecretKeyPtr secretKey $ \secretKeyPtr -> withByteArray (v2Ciphertext envelope) $ \ct -> @@ -694,8 +693,7 @@ decryptKeyMaterialV2 eKey pass = (coerce np) (coerce kp) if status /= 0 - then do - mlsbFinalize (unSecretKey secretKey) + then pure $ Left XPrvAuthenticationFailed else pure $ From f93c85e3472ec224008969b4d44d47f5b3d37dfa Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 11:31:44 +0300 Subject: [PATCH 15/33] Rename "Encrypted" -> "Secret/Unencrypted" * `ekey` -> `skey` * `ENCRYPTED_KEY_SIZE` -> `UNENCRYPTED_KEY_SIZE` It was incorrectly named, since secret key in that structure is not encrypted --- cardano-crypto-wallet/cbits/encrypted_sign.c | 36 ++++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cardano-crypto-wallet/cbits/encrypted_sign.c b/cardano-crypto-wallet/cbits/encrypted_sign.c index f68a6b8e8..5fab0e384 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -16,14 +16,14 @@ 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; @@ -34,7 +34,7 @@ typedef struct { } 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 + * 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 (const ed25519_secret_key secret_key, @@ -43,7 +43,7 @@ static void wallet_encrypted_initialize { ed25519_public_key pub_key; cardano_crypto_ed25519_publickey(secret_key, pub_key); - memcpy(out->ekey, secret_key, ENCRYPTED_KEY_SIZE); + memcpy(out->skey, secret_key, UNENCRYPTED_KEY_SIZE); memcpy(out->pkey, pub_key, PUBLIC_KEY_SIZE); memcpy(out->cc, cc, CHAIN_CODE_SIZE); } @@ -85,13 +85,13 @@ int cardano_wallet_encrypted_decrypt { ed25519_public_key pub_key; - cardano_crypto_ed25519_publickey(in->ekey, pub_key); + cardano_crypto_ed25519_publickey(in->skey, pub_key); if (sodium_memcmp(pub_key, in->pkey, PUBLIC_KEY_SIZE) != 0) { secure_clear(pub_key, sizeof(pub_key)); return 1; } - memcpy(out->ekey, in->ekey, ENCRYPTED_KEY_SIZE); + memcpy(out->skey, in->skey, UNENCRYPTED_KEY_SIZE); memcpy(out->pkey, in->pkey, PUBLIC_KEY_SIZE); memcpy(out->cc, in->cc, CHAIN_CODE_SIZE); @@ -105,8 +105,8 @@ int cardano_wallet_encrypted_sign ed25519_signature signature) { ed25519_public_key pub_key; - cardano_crypto_ed25519_publickey(in->ekey, pub_key); - cardano_crypto_ed25519_sign(data, data_len, in->cc, CHAIN_CODE_SIZE, in->ekey, pub_key, signature); + cardano_crypto_ed25519_publickey(in->skey, pub_key); + cardano_crypto_ed25519_sign(data, data_len, in->cc, CHAIN_CODE_SIZE, in->skey, pub_key, signature); secure_clear(pub_key, sizeof(pub_key)); return 0; } @@ -259,7 +259,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 +267,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 +282,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); @@ -292,8 +292,8 @@ int cardano_wallet_encrypted_derive_private wallet_encrypted_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)); @@ -386,12 +386,12 @@ int wallet_sodium_xchacha20poly1305_encrypt( 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) { + if (plaintext_len != UNENCRYPTED_KEY_SIZE) { return 1; } if (crypto_aead_xchacha20poly1305_ietf_encrypt( @@ -424,12 +424,12 @@ 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) { + if (ciphertext_len != UNENCRYPTED_KEY_SIZE) { return 1; } memcpy(combined, ciphertext, (size_t) ciphertext_len); From 21c39fd49d1b1df407d1475705002f8b12dcfffe Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 14:12:55 +0300 Subject: [PATCH 16/33] Simplify validation of `KeyMaterial` There was a totally unnecessary allocation of key material legacy struct --- cardano-crypto-wallet/cbits/encrypted_sign.c | 12 +++-------- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 21 ++++++++----------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/cardano-crypto-wallet/cbits/encrypted_sign.c b/cardano-crypto-wallet/cbits/encrypted_sign.c index 5fab0e384..44ef51f5b 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -79,22 +79,16 @@ int cardano_wallet_encrypted_new_from_mkg /* Validate that the public key in the struct 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 cardano_wallet_validate + (encrypted_key const *in) { ed25519_public_key pub_key; cardano_crypto_ed25519_publickey(in->skey, pub_key); if (sodium_memcmp(pub_key, in->pkey, PUBLIC_KEY_SIZE) != 0) { - secure_clear(pub_key, sizeof(pub_key)); + secure_clear(pub_key, sizeof(pub_key)); return 1; } - - memcpy(out->skey, in->skey, UNENCRYPTED_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; } diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 0f8453c41..cdb72dfd4 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -743,17 +743,15 @@ wrapKeyMaterial pass material = do encodeV2Envelope $ V2Envelope salt nonce (kmPublicKey material) (kmChainCode material) ciphertext tag +-- | Verify that associated public key matches the secret key in the `KeyMaterial` validateKeyMaterial :: KeyMaterial Unchecked -> IO (Either XPrvError (KeyMaterial Validated)) validateKeyMaterial keyMaterial@KeyMaterial {..} = - withLegacyStruct keyMaterial $ \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 (KeyMaterial {..}) + withLegacyStruct keyMaterial $ \inPtr -> do + r <- wallet_validate (coerce inPtr) + pure $ + if r /= 0 + then Left XPrvPublicKeyMismatch + else Right (KeyMaterial {..}) -- --------------------------------------------------------------------------- -- Internal: locked memory helpers @@ -935,9 +933,8 @@ foreign import ccall "cardano_wallet_encrypted_new_from_mkg" EncryptedKeyPtr -> IO CInt -foreign import ccall "cardano_wallet_encrypted_decrypt" - wallet_encrypted_decrypt :: - EncryptedKeyPtr -> +foreign import ccall "cardano_wallet_validate" + wallet_validate :: EncryptedKeyPtr -> IO CInt From e67db24e559b580d1717834debe5aa86616cd06c Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 15:29:15 +0300 Subject: [PATCH 17/33] Introduce `KeyMaterialBuffer` Also rename `encrypted_key` -> `key_material` f --- cardano-crypto-wallet/cbits/encrypted_sign.c | 18 +- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 164 ++++++++++-------- 2 files changed, 98 insertions(+), 84 deletions(-) diff --git a/cardano-crypto-wallet/cbits/encrypted_sign.c b/cardano-crypto-wallet/cbits/encrypted_sign.c index 44ef51f5b..1856bc74e 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -26,20 +26,20 @@ typedef struct { 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. +/* 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 (const ed25519_secret_key secret_key, const uint8_t cc[CHAIN_CODE_SIZE], - encrypted_key *out) + key_material *out) { ed25519_public_key pub_key; cardano_crypto_ed25519_publickey(secret_key, pub_key); @@ -51,7 +51,7 @@ static void wallet_encrypted_initialize int cardano_wallet_encrypted_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 (cardano_crypto_ed25519_extend(seed, secret_key)) { @@ -65,7 +65,7 @@ int cardano_wallet_encrypted_from_secret int cardano_wallet_encrypted_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); @@ -80,7 +80,7 @@ int cardano_wallet_encrypted_new_from_mkg /* Validate that the public key in the struct matches the secret key. * Returns 0 on success (keys consistent), 1 on mismatch. */ int cardano_wallet_validate - (encrypted_key const *in) + (key_material const *in) { ed25519_public_key pub_key; @@ -94,7 +94,7 @@ int cardano_wallet_validate } int cardano_wallet_encrypted_sign - (encrypted_key const *in, + (key_material const *in, uint8_t const *data, uint32_t const data_len, ed25519_signature signature) { @@ -241,9 +241,9 @@ static void add_left_public(uint8_t *out, uint8_t *z, uint8_t *in, derivation_sc } int cardano_wallet_encrypted_derive_private - (encrypted_key const *in, + (key_material const *in, uint32_t index, - encrypted_key *out, + key_material *out, derivation_scheme_mode mode) { ed25519_secret_key priv_key; diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index cdb72dfd4..026ae3686 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -6,6 +6,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeData #-} +{-# LANGUAGE TypeOperators #-} -- | -- Module : Cardano.Crypto.WalletHD.Encrypted @@ -101,7 +102,7 @@ import Foreign.C.Types import Foreign.Marshal.Utils (copyBytes) import Foreign.Ptr import GHC.Stack (HasCallStack) -import GHC.TypeLits (natVal) +import GHC.TypeLits import System.IO.Unsafe (unsafePerformIO) import Cardano.Binary (toCBOR) @@ -143,6 +144,9 @@ legacyKeySize, signatureSize :: Int legacyKeySize = 64 signatureSize = 64 +mlsbCreate :: KnownNat n => (MLockedSizedBytes n -> b) -> (b -> IO c) -> IO c +mlsbCreate mkType action = bracket mlsbNewZero mlsbFinalize (action . mkType) + ------------------------------------------------------------------------------ -- SECRET_KEY ------------------------------------------------------------------------------ @@ -152,16 +156,14 @@ type SECRET_KEY_SIZE = 64 secretKeySize :: Int secretKeySize = fromInteger (natVal (Proxy @SECRET_KEY_SIZE)) -newtype SecretKey = SecretKey {unSecretKey :: MLockedSizedBytes SECRET_KEY_SIZE} +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 #-} -finalizeSecretKey :: SecretKey -> IO () -finalizeSecretKey = mlsbFinalize . unSecretKey - ------------------------------------------------------------------------------ -- PUBLIC_KEY ------------------------------------------------------------------------------ @@ -173,6 +175,7 @@ publicKeySize = fromInteger (natVal (Proxy @PUBLIC_KEY_SIZE)) 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 = @@ -202,6 +205,7 @@ 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 = @@ -220,15 +224,38 @@ 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) + type Salt = ByteString type Nonce = ByteString type Ciphertext = ByteString type AuthenticationTag = ByteString type AadContext = ByteString -legacyTotalKeySize :: Int -legacyTotalKeySize = legacyKeySize + publicKeySize + chainCodeSize - -- --------------------------------------------------------------------------- -- V2 envelope constants -- --------------------------------------------------------------------------- @@ -352,21 +379,8 @@ data V2Envelope = V2Envelope } deriving (Eq, Show) -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 - } - -- 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) @@ -400,7 +414,7 @@ validateSerializedKey eKey = encryptedKeyFormat :: EncryptedKey -> XPrvFormat encryptedKeyFormat (EncryptedKey bs) - | BS.length bs == legacyTotalKeySize = LegacyV1 + | BS.length bs == keyMaterialSize = LegacyV1 | otherwise = EnvelopeV2 unEncryptedKey :: EncryptedKey -> ByteString @@ -440,12 +454,12 @@ encryptedSign :: EncryptedKey -> passphrase -> msg -> IO (Either XPrvError Signature) encryptedSign eKey pass msg = withDecryptedKeyMaterial eKey pass $ \keyMaterial -> - withLegacyStruct keyMaterial $ \legacyStructPtr -> do + withKeyMaterialPtr keyMaterial $ \cKeyMaterialPtr -> do (status, sig) <- B.allocRet signatureSize $ \outSig -> withByteArray msg $ \msgPtr -> wallet_encrypted_sign - (coerce legacyStructPtr) + cKeyMaterialPtr msgPtr (fromIntegral $ B.length msg) (coerce outSig) @@ -652,7 +666,7 @@ withDecryptedKeyMaterial ekey pass action = case encryptedKeyFormat ekey of LegacyV1 -> pure (Left XPrvDecodeError) EnvelopeV2 -> - bracket (SecretKey <$> mlsbNewZero) finalizeSecretKey $ \secretKey -> + mlsbCreate SecretKey $ \secretKey -> decryptKeyMaterialV2 secretKey ekey pass >>= \case Left err -> pure $ Left err Right uncheckedKeyMaterial -> @@ -746,8 +760,8 @@ wrapKeyMaterial pass material = do -- | Verify that associated public key matches the secret key in the `KeyMaterial` validateKeyMaterial :: KeyMaterial Unchecked -> IO (Either XPrvError (KeyMaterial Validated)) validateKeyMaterial keyMaterial@KeyMaterial {..} = - withLegacyStruct keyMaterial $ \inPtr -> do - r <- wallet_validate (coerce inPtr) + withKeyMaterialPtr keyMaterial $ \cKeyMaterialPtr -> do + r <- wallet_validate cKeyMaterialPtr pure $ if r /= 0 then Left XPrvPublicKeyMismatch @@ -760,51 +774,51 @@ validateKeyMaterial keyMaterial@KeyMaterial {..} = -- | 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 v -> (Ptr Word8 -> IO r) -> IO r -withLegacyStruct KeyMaterial {kmSecretKey, kmPublicKey, kmChainCode} action = - bracket (mlsbNewZero :: IO (MLockedSizedBytes 128)) mlsbFinalize $ \mlsb -> - mlsbUseAsCPtr mlsb $ \base -> do - withSecretKeyPtr kmSecretKey $ \(SecretKeyPtr skPtr) -> - copyBytes base skPtr secretKeySize - withPublicKeyPtr kmPublicKey $ \(PublicKeyPtr pkPtr) -> - copyBytes (base `plusPtr` secretKeySize) (castPtr pkPtr) publicKeySize - withChainCodePtr kmChainCode $ \(ChainCodePtr ccPtr) -> - copyBytes (base `plusPtr` (secretKeySize + publicKeySize)) (castPtr ccPtr) chainCodeSize - action base +withKeyMaterialPtr :: KeyMaterial v -> (KeyMaterialPtr -> IO r) -> IO r +withKeyMaterialPtr KeyMaterial {kmSecretKey, kmPublicKey, kmChainCode} action = + allocaKeyMaterialBuffer $ \ptr@(KeyMaterialPtr cKeyMaterialPtr) -> do + withSecretKeyPtr kmSecretKey $ \(SecretKeyPtr skPtr) -> + copyBytes cKeyMaterialPtr skPtr secretKeySize + withPublicKeyPtr kmPublicKey $ \(PublicKeyPtr pkPtr) -> + copyBytes (cKeyMaterialPtr `plusPtr` secretKeySize) (castPtr pkPtr) publicKeySize + withChainCodePtr kmChainCode $ \(ChainCodePtr ccPtr) -> + copyBytes (cKeyMaterialPtr `plusPtr` (secretKeySize + publicKeySize)) (castPtr 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 -> + -- | Action that will use the newly populated `KeyMaterial` (KeyMaterial Validated -> IO (Either XPrvError a)) -> - (Ptr Word8 -> IO CInt) -> + -- | 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) -withEncryptedKeyOutput onFailure keyMaterialAction structPtrAction = - bracket (mlsbNewZero :: IO (MLockedSizedBytes 128)) mlsbFinalize $ \tmpMlsb -> do - r <- mlsbUseAsCPtr tmpMlsb structPtrAction +withNewKeyMaterial onFailure keyMaterialAction fillKeyMaterialPtrAction = + allocaKeyMaterialBuffer $ \cKeyMaterialPtr@(KeyMaterialPtr inPtr) -> do + r <- fillKeyMaterialPtrAction cKeyMaterialPtr if r /= 0 then pure (Left onFailure) - else mlsbUseAsCPtr tmpMlsb $ \tmpPtr -> do - bracket mlsbNewZero mlsbFinalize $ \secretKey -> do - mlsbUseAsCPtr secretKey $ \skPtr -> copyBytes skPtr tmpPtr secretKeySize - publicKey <- - psbCreate $ \pkPtr -> - copyBytes pkPtr (tmpPtr `plusPtr` secretKeySize) publicKeySize - chainCode <- - psbCreate $ \ccPtr -> - copyBytes ccPtr (tmpPtr `plusPtr` (secretKeySize + publicKeySize)) chainCodeSize - eKeyMaterial <- - validateKeyMaterial $ - KeyMaterial - { kmSecretKey = SecretKey secretKey - , kmPublicKey = PublicKey publicKey - , kmChainCode = ChainCode chainCode - } - case eKeyMaterial of - Left err -> pure $ Left err - Right keyMaterial -> keyMaterialAction keyMaterial + 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) @@ -817,7 +831,7 @@ legacyMaterialFromSecret :: (KeyMaterial Validated -> IO (Either XPrvError a)) -> IO (Either XPrvError a) legacyMaterialFromSecret sec cc action = - withEncryptedKeyOutput XPrvInvalidSecretKey action $ \outPtr -> + withNewKeyMaterial XPrvInvalidSecretKey action $ \outPtr -> withByteArray sec $ \psec -> withByteArray cc $ \pcc -> wallet_encrypted_from_secret (coerce psec) (coerce pcc) (coerce outPtr) @@ -828,7 +842,7 @@ legacyMaterialFromMasterKey :: (KeyMaterial Validated -> IO (Either XPrvError a)) -> IO (Either XPrvError a) legacyMaterialFromMasterKey sec action = - withEncryptedKeyOutput XPrvInvalidSecretKey action $ \outPtr -> + withNewKeyMaterial XPrvInvalidSecretKey action $ \outPtr -> withByteArray sec $ \psec -> wallet_encrypted_new_from_mkg (MasterKeyPtr psec) (coerce outPtr) @@ -839,10 +853,10 @@ legacyDerivePrivate :: (KeyMaterial Validated -> IO (Either XPrvError a)) -> IO (Either XPrvError a) legacyDerivePrivate dscheme parent childIndex action = - withLegacyStruct parent $ \inPtr -> - withEncryptedKeyOutput XPrvInternalError action $ \outPtr -> + withKeyMaterialPtr parent $ \inPtr -> + withNewKeyMaterial XPrvInternalError action $ \outPtr -> wallet_encrypted_derive_private - (coerce inPtr) + inPtr childIndex (coerce outPtr) (dschemeToC dscheme) @@ -924,23 +938,23 @@ foreign import ccall "cardano_wallet_encrypted_from_secret" wallet_encrypted_from_secret :: SecretKeyPtr -> ChainCodePtr -> - EncryptedKeyPtr -> + KeyMaterialPtr -> IO CInt foreign import ccall "cardano_wallet_encrypted_new_from_mkg" wallet_encrypted_new_from_mkg :: MasterKeyPtr -> - EncryptedKeyPtr -> + KeyMaterialPtr -> IO CInt foreign import ccall "cardano_wallet_validate" wallet_validate :: - EncryptedKeyPtr -> + KeyMaterialPtr -> IO CInt foreign import ccall "cardano_wallet_encrypted_sign" wallet_encrypted_sign :: - EncryptedKeyPtr -> + KeyMaterialPtr -> Ptr Word8 -> Word32 -> SignaturePtr -> @@ -948,9 +962,9 @@ foreign import ccall "cardano_wallet_encrypted_sign" foreign import ccall "cardano_wallet_encrypted_derive_private" wallet_encrypted_derive_private :: - EncryptedKeyPtr -> + KeyMaterialPtr -> DerivationIndex -> - EncryptedKeyPtr -> + KeyMaterialPtr -> CDerivationScheme -> IO CInt From d922ac1de685511114df24640acd9275bc870a51 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 15:51:54 +0300 Subject: [PATCH 18/33] Avoid unnecessarily allocating `CKeyMaterialBuffer` There was need to make a copy of the full `KeyMaterial` into MLocked memory, which is scarce, just to validate public key against a secret key --- cardano-crypto-wallet/cbits/encrypted_sign.c | 9 ++--- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 34 ++++++++++--------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/cardano-crypto-wallet/cbits/encrypted_sign.c b/cardano-crypto-wallet/cbits/encrypted_sign.c index 1856bc74e..399af45f9 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -77,15 +77,16 @@ int cardano_wallet_encrypted_new_from_mkg 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_validate - (key_material const *in) + (const uint8_t skey[UNENCRYPTED_KEY_SIZE], + const uint8_t pkey[PUBLIC_KEY_SIZE]) { ed25519_public_key pub_key; - cardano_crypto_ed25519_publickey(in->skey, pub_key); - if (sodium_memcmp(pub_key, in->pkey, PUBLIC_KEY_SIZE) != 0) { + cardano_crypto_ed25519_publickey(skey, pub_key); + if (sodium_memcmp(pub_key, pkey, PUBLIC_KEY_SIZE) != 0) { secure_clear(pub_key, sizeof(pub_key)); return 1; } diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 026ae3686..1dc2463b7 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -454,12 +454,12 @@ encryptedSign :: EncryptedKey -> passphrase -> msg -> IO (Either XPrvError Signature) encryptedSign eKey pass msg = withDecryptedKeyMaterial eKey pass $ \keyMaterial -> - withKeyMaterialPtr keyMaterial $ \cKeyMaterialPtr -> do + withKeyMaterialPtr keyMaterial $ \keyMaterialPtr -> do (status, sig) <- B.allocRet signatureSize $ \outSig -> withByteArray msg $ \msgPtr -> wallet_encrypted_sign - cKeyMaterialPtr + keyMaterialPtr msgPtr (fromIntegral $ B.length msg) (coerce outSig) @@ -759,13 +759,14 @@ wrapKeyMaterial pass material = do -- | Verify that associated public key matches the secret key in the `KeyMaterial` validateKeyMaterial :: KeyMaterial Unchecked -> IO (Either XPrvError (KeyMaterial Validated)) -validateKeyMaterial keyMaterial@KeyMaterial {..} = - withKeyMaterialPtr keyMaterial $ \cKeyMaterialPtr -> do - r <- wallet_validate cKeyMaterialPtr - pure $ - if r /= 0 - then Left XPrvPublicKeyMismatch - else Right (KeyMaterial {..}) +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 @@ -776,13 +777,13 @@ validateKeyMaterial keyMaterial@KeyMaterial {..} = -- and freed when the action returns (normally or via exception). withKeyMaterialPtr :: KeyMaterial v -> (KeyMaterialPtr -> IO r) -> IO r withKeyMaterialPtr KeyMaterial {kmSecretKey, kmPublicKey, kmChainCode} action = - allocaKeyMaterialBuffer $ \ptr@(KeyMaterialPtr cKeyMaterialPtr) -> do + allocaKeyMaterialBuffer $ \ptr@(KeyMaterialPtr keyMaterialPtr) -> do withSecretKeyPtr kmSecretKey $ \(SecretKeyPtr skPtr) -> - copyBytes cKeyMaterialPtr skPtr secretKeySize + copyBytes keyMaterialPtr skPtr secretKeySize withPublicKeyPtr kmPublicKey $ \(PublicKeyPtr pkPtr) -> - copyBytes (cKeyMaterialPtr `plusPtr` secretKeySize) (castPtr pkPtr) publicKeySize + copyBytes (keyMaterialPtr `plusPtr` secretKeySize) pkPtr publicKeySize withChainCodePtr kmChainCode $ \(ChainCodePtr ccPtr) -> - copyBytes (cKeyMaterialPtr `plusPtr` (secretKeySize + publicKeySize)) (castPtr ccPtr) chainCodeSize + copyBytes (keyMaterialPtr `plusPtr` (secretKeySize + publicKeySize)) ccPtr chainCodeSize action ptr -- | Call a C function that writes a 128-byte @encrypted_key@ struct to the @@ -797,8 +798,8 @@ withNewKeyMaterial :: (KeyMaterialPtr -> IO CInt) -> IO (Either XPrvError a) withNewKeyMaterial onFailure keyMaterialAction fillKeyMaterialPtrAction = - allocaKeyMaterialBuffer $ \cKeyMaterialPtr@(KeyMaterialPtr inPtr) -> do - r <- fillKeyMaterialPtrAction cKeyMaterialPtr + allocaKeyMaterialBuffer $ \keyMaterialPtr@(KeyMaterialPtr inPtr) -> do + r <- fillKeyMaterialPtrAction keyMaterialPtr if r /= 0 then pure (Left onFailure) else mlsbCreate SecretKey $ \secretKey -> do @@ -949,7 +950,8 @@ foreign import ccall "cardano_wallet_encrypted_new_from_mkg" foreign import ccall "cardano_wallet_validate" wallet_validate :: - KeyMaterialPtr -> + SecretKeyPtr -> + PublicKeyPtr -> IO CInt foreign import ccall "cardano_wallet_encrypted_sign" From 750d89dbe305a3ff13251d709c5809f6a5a1f485 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 15:57:16 +0300 Subject: [PATCH 19/33] Remove confusing `encrypted_` suffix --- cardano-crypto-wallet/cbits/encrypted_sign.c | 18 +++++------ .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 30 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/cardano-crypto-wallet/cbits/encrypted_sign.c b/cardano-crypto-wallet/cbits/encrypted_sign.c index 399af45f9..c98719908 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -36,7 +36,7 @@ typedef struct { /* 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], key_material *out) @@ -48,7 +48,7 @@ static void wallet_encrypted_initialize memcpy(out->cc, cc, CHAIN_CODE_SIZE); } -int cardano_wallet_encrypted_from_secret +int cardano_wallet_from_secret (const uint8_t seed[SECRET_KEY_SEED_SIZE], const uint8_t cc[CHAIN_CODE_SIZE], key_material *out) @@ -58,12 +58,12 @@ int cardano_wallet_encrypted_from_secret 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 cardano_wallet_new_from_mkg (const uint8_t master_key[96], key_material *out) { @@ -72,7 +72,7 @@ int cardano_wallet_encrypted_new_from_mkg 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; } @@ -94,7 +94,7 @@ int cardano_wallet_validate return 0; } -int cardano_wallet_encrypted_sign +int cardano_wallet_sign (key_material const *in, uint8_t const *data, uint32_t const data_len, ed25519_signature signature) @@ -241,7 +241,7 @@ static void add_left_public(uint8_t *out, uint8_t *z, uint8_t *in, derivation_sc cardano_crypto_ed25519_point_add(pub_zl8, in, out); } -int cardano_wallet_encrypted_derive_private +int cardano_wallet_derive_private (key_material const *in, uint32_t index, key_material *out, @@ -285,7 +285,7 @@ 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, UNENCRYPTED_KEY_SIZE); secure_clear(res_key, UNENCRYPTED_KEY_SIZE); @@ -296,7 +296,7 @@ int cardano_wallet_encrypted_derive_private return 0; } -int cardano_wallet_encrypted_derive_public +int cardano_wallet_derive_public (uint8_t *pub_in, uint8_t *cc_in, uint32_t index, diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 1dc2463b7..17c8abe0c 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -458,7 +458,7 @@ encryptedSign eKey pass msg = (status, sig) <- B.allocRet signatureSize $ \outSig -> withByteArray msg $ \msgPtr -> - wallet_encrypted_sign + wallet_sign keyMaterialPtr msgPtr (fromIntegral $ B.length msg) @@ -491,7 +491,7 @@ encryptedDerivePublic dscheme (publicKey, cc) childIndex withPublicKeyPtr publicKey $ \publicKeyPtr -> withChainCodePtr cc $ \chainCodePtr -> do r <- - wallet_encrypted_derive_public + wallet_derive_public publicKeyPtr chainCodePtr childIndex @@ -835,7 +835,7 @@ 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) (coerce outPtr) legacyMaterialFromMasterKey :: ByteArrayAccess secret => @@ -845,7 +845,7 @@ legacyMaterialFromMasterKey :: 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) (coerce outPtr) legacyDerivePrivate :: DerivationScheme -> @@ -856,7 +856,7 @@ legacyDerivePrivate :: legacyDerivePrivate dscheme parent childIndex action = withKeyMaterialPtr parent $ \inPtr -> withNewKeyMaterial XPrvInternalError action $ \outPtr -> - wallet_encrypted_derive_private + wallet_derive_private inPtr childIndex (coerce outPtr) @@ -935,15 +935,15 @@ failDecoder = fail . show -- FFI declarations -- --------------------------------------------------------------------------- -foreign import ccall "cardano_wallet_encrypted_from_secret" - wallet_encrypted_from_secret :: +foreign import ccall "cardano_wallet_from_secret" + wallet_from_secret :: SecretKeyPtr -> ChainCodePtr -> KeyMaterialPtr -> IO CInt -foreign import ccall "cardano_wallet_encrypted_new_from_mkg" - wallet_encrypted_new_from_mkg :: +foreign import ccall "cardano_wallet_new_from_mkg" + wallet_new_from_mkg :: MasterKeyPtr -> KeyMaterialPtr -> IO CInt @@ -954,24 +954,24 @@ foreign import ccall "cardano_wallet_validate" PublicKeyPtr -> IO CInt -foreign import ccall "cardano_wallet_encrypted_sign" - wallet_encrypted_sign :: +foreign import ccall "cardano_wallet_sign" + wallet_sign :: KeyMaterialPtr -> Ptr Word8 -> Word32 -> SignaturePtr -> IO CInt -foreign import ccall "cardano_wallet_encrypted_derive_private" - wallet_encrypted_derive_private :: +foreign import ccall "cardano_wallet_derive_private" + wallet_derive_private :: KeyMaterialPtr -> DerivationIndex -> KeyMaterialPtr -> CDerivationScheme -> IO CInt -foreign import ccall "cardano_wallet_encrypted_derive_public" - wallet_encrypted_derive_public :: +foreign import ccall "cardano_wallet_derive_public" + wallet_derive_public :: PublicKeyPtr -> ChainCodePtr -> DerivationIndex -> From 38445e0e6a9d99155f5ca54cae6d7843c3d5282e Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 16:14:57 +0300 Subject: [PATCH 20/33] Remove redundant `legacyKeySize` --- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 17c8abe0c..41e030cbf 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -121,6 +121,7 @@ import Codec.CBOR.Decoding ( import Codec.CBOR.Encoding ( Encoding, encodeBytes, + encodeInt, encodeListLen, encodeWord, ) @@ -140,8 +141,7 @@ data DerivationScheme = DerivationScheme1 | DerivationScheme2 -- Size constants -- --------------------------------------------------------------------------- -legacyKeySize, signatureSize :: Int -legacyKeySize = 64 +signatureSize :: Int signatureSize = 64 mlsbCreate :: KnownNat n => (MLockedSizedBytes n -> b) -> (b -> IO c) -> IO c @@ -506,7 +506,7 @@ encryptedDerivePublic dscheme (publicKey, cc) childIndex encryptedPublic :: HasCallStack => EncryptedKey -> PublicKey encryptedPublic eKey@(EncryptedKey eKeyBytes) = case encryptedKeyFormat eKey of - LegacyV1 -> errorFail $ mkPublicKey $ sub legacyKeySize publicKeySize eKeyBytes + LegacyV1 -> errorFail $ mkPublicKey $ sub secretKeySize publicKeySize eKeyBytes EnvelopeV2 -> either (const badEnvelope) v2PublicKey (decodeV2Envelope eKey) where badEnvelope = error "encryptedPublic: invalid v2 envelope" @@ -515,7 +515,7 @@ encryptedChainCode :: HasCallStack => EncryptedKey -> ChainCode encryptedChainCode eKey@(EncryptedKey eKeyBytes) = case encryptedKeyFormat eKey of LegacyV1 -> - errorFail $ mkChainCode $ sub (legacyKeySize + publicKeySize) chainCodeSize eKeyBytes + errorFail $ mkChainCode $ sub (secretKeySize + publicKeySize) chainCodeSize eKeyBytes EnvelopeV2 -> either (const badEnvelope) v2ChainCode (decodeV2Envelope eKey) where badEnvelope = error "encryptedChainCode: invalid v2 envelope" @@ -558,7 +558,7 @@ decodeEnvelope = do when (BS.length nonce /= nonceSize) (failDecoder XPrvInvalidNonceLength) aad <- decodeBytes ciphertext <- decodeBytes - when (BS.length ciphertext /= legacyKeySize) (failDecoder XPrvInvalidCiphertextLength) + when (BS.length ciphertext /= secretKeySize) (failDecoder XPrvInvalidCiphertextLength) tag <- decodeBytes when (BS.length tag /= tagSize) (failDecoder XPrvInvalidTagLength) (pub, cc) <- either failDecoder pure $ decodeAad aad @@ -606,7 +606,7 @@ encodeAad publicKey cc = , encodeWord productionArgonOutputLength , encodeWord xchacha20poly1305Id , encodeWord 1 - , encodeWord (fromIntegral legacyKeySize) + , encodeInt secretKeySize , encodePublicKey publicKey , encodeChainCode cc ] @@ -642,7 +642,7 @@ decodeAadFields = do payloadKind <- decodeWord when (payloadKind /= 1) (failDecoder XPrvDecodeError) payloadLen <- decodeWord - when (payloadLen /= fromIntegral legacyKeySize) (failDecoder XPrvInvalidCiphertextLength) + when (payloadLen /= fromIntegral secretKeySize) (failDecoder XPrvInvalidCiphertextLength) pubKeyBytes <- decodeBytes chainCodeBytes <- decodeBytes case mkPublicKey pubKeyBytes of @@ -734,7 +734,7 @@ wrapKeyMaterial pass material = do let aad = encodeAad (kmPublicKey material) (kmChainCode material) withSecretKeyPtr (kmSecretKey material) $ \skPtr -> do ((status, tag), ciphertext) <- - B.allocRet legacyKeySize $ \outCipher -> + B.allocRet secretKeySize $ \outCipher -> B.allocRet tagSize $ \outTag -> withByteArray aad $ \ad -> withByteArray nonce $ \np -> @@ -743,7 +743,7 @@ wrapKeyMaterial pass material = do (coerce outCipher) (coerce outTag) skPtr - (fromIntegral @Int @CULLong legacyKeySize) + (fromIntegral @Int @CULLong secretKeySize) ad (fromIntegral @Int @CULLong $ BS.length aad) (coerce np) From bee08e4e3986fae6a4d2042d8e2515ce4a1b5379 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 17:15:58 +0300 Subject: [PATCH 21/33] More type safety for `Nonce` and `Salt` --- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 208 ++++++++++++------ 1 file changed, 145 insertions(+), 63 deletions(-) diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 41e030cbf..b3fff4506 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -33,16 +33,32 @@ module Cardano.Crypto.WalletHD.Encrypted ( -- ** PublicKey PublicKey, + publicKeySize, mkPublicKey, publicKeyByteArray, publicKeyByteString, -- ** ChainCode ChainCode, + chainCodeSize, mkChainCode, chainCodeByteArray, chainCodeByteString, + -- ** Salt + Salt, + saltSize, + mkSalt, + saltByteArray, + saltByteString, + + -- ** Nonce + Nonce, + nonceSize, + mkNonce, + nonceByteArray, + nonceByteString, + -- * Construction & validation encryptedCreate, encryptedCreateDirectWithTweak, @@ -73,11 +89,14 @@ import Cardano.Crypto.PinnedSizedBytes ( PinnedSizedBytes, psbCreate, psbCreateResult, + psbCreateResultLen, + psbFromByteString, psbFromByteStringM, psbToByteArray, psbToByteString, psbUseAsCPtr, ) +import Control.Arrow (first) import Control.DeepSeq import Control.Exception (bracket) import Control.Monad (when) @@ -250,8 +269,6 @@ allocaKeyMaterialBuffer action = mlsbCreate KeyMaterialBuffer $ \(KeyMaterialBuffer keyMaterialBuffer) -> mlsbUseAsCPtr keyMaterialBuffer (action . KeyMaterialPtr) -type Salt = ByteString -type Nonce = ByteString type Ciphertext = ByteString type AuthenticationTag = ByteString type AadContext = ByteString @@ -294,9 +311,7 @@ productionArgonTimeCost = kdfTimeCost productionKdfParams productionArgonParallelism = kdfParallelism productionKdfParams productionArgonOutputLength = kdfOutputLength productionKdfParams -saltSize, nonceSize, tagSize :: Int -saltSize = 32 -nonceSize = 24 +tagSize :: Int tagSize = 16 -- --------------------------------------------------------------------------- @@ -369,6 +384,80 @@ newtype EncryptedKey = EncryptedKey ByteString -- V2 envelope data -- --------------------------------------------------------------------------- +------------------------------------------------------------------------------ +-- 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) + +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 +------------------------------------------------------------------------------ + +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 + data V2Envelope = V2Envelope { v2Salt :: !Salt , v2Nonce :: !Nonce @@ -383,8 +472,6 @@ data V2Envelope = V2Envelope newtype MasterKeyPtr = MasterKeyPtr (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) @@ -485,23 +572,21 @@ encryptedDerivePublic dscheme (publicKey, cc) childIndex | childIndex >= 0x80000000 = error "encryptedDerivePublic: cannot derive hardened key from public key" | otherwise = unsafePerformIO $ do - (newPublicKey, newChainCode) <- - psbCreateResult $ \publicKeyPtrOut -> - 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 () - pure (PublicKey newPublicKey, ChainCode newChainCode) + 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) = @@ -550,12 +635,10 @@ 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 /= secretKeySize) (failDecoder XPrvInvalidCiphertextLength) @@ -584,9 +667,9 @@ encodeV2Envelope envelope = , encodeWord productionArgonTimeCost , encodeWord productionArgonParallelism , encodeWord productionArgonOutputLength - , encodeBytes (v2Salt envelope) + , encodeSalt (v2Salt envelope) , encodeWord xchacha20poly1305Id - , encodeBytes (v2Nonce envelope) + , encodeNonce (v2Nonce envelope) , encodeBytes (encodeAad (v2PublicKey envelope) (v2ChainCode envelope)) , encodeBytes (v2Ciphertext envelope) , encodeBytes (v2Tag envelope) @@ -676,7 +759,7 @@ withDecryptedKeyMaterial ekey pass action = decryptKeyMaterialV2 :: ByteArrayAccess passphrase => - -- | SecretKey that will be populated from EncryptedKey + -- | Empty SecretKey that will be populated from EncryptedKey SecretKey -> EncryptedKey -> passphrase -> @@ -695,7 +778,7 @@ decryptKeyMaterialV2 secretKey eKey pass = withByteArray (v2Ciphertext envelope) $ \ct -> withByteArray (v2Tag envelope) $ \tg -> withByteArray aad $ \ad -> - withByteArray (v2Nonce envelope) $ \np -> + withNoncePtr (v2Nonce envelope) $ \noncePtr -> withByteArray wrappingKey $ \kp -> wallet_sodium_xchacha20poly1305_decrypt secretKeyPtr @@ -704,7 +787,7 @@ decryptKeyMaterialV2 secretKey eKey pass = (coerce tg) ad (fromIntegral @Int @CULLong $ BS.length aad) - (coerce np) + noncePtr (coerce kp) if status /= 0 then @@ -722,8 +805,8 @@ wrapKeyMaterial :: ByteArrayAccess passphrase => passphrase -> KeyMaterial Validated -> IO (Either XPrvError EncryptedKey) wrapKeyMaterial pass material = do - eSalt <- randomBytesIO saltSize - eNonce <- randomBytesIO nonceSize + eSalt <- fmap Salt <$> randomBytesIO + eNonce <- fmap Nonce <$> randomBytesIO case (,) <$> eSalt <*> eNonce of Left err -> pure (Left err) Right (salt, nonce) -> do @@ -737,7 +820,7 @@ wrapKeyMaterial pass material = do B.allocRet secretKeySize $ \outCipher -> B.allocRet tagSize $ \outTag -> withByteArray aad $ \ad -> - withByteArray nonce $ \np -> + withNoncePtr nonce $ \noncePtr -> withByteArray wrappingKey $ \kp -> wallet_sodium_xchacha20poly1305_encrypt (coerce outCipher) @@ -746,7 +829,7 @@ wrapKeyMaterial pass material = do (fromIntegral @Int @CULLong secretKeySize) ad (fromIntegral @Int @CULLong $ BS.length aad) - (coerce np) + noncePtr (coerce kp) if status /= 0 then pure (Left XPrvInternalError) @@ -868,37 +951,36 @@ legacyDerivePrivate dscheme parent childIndex action = deriveWrappingKey :: 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 -> IO (Either XPrvError B.ScrubbedBytes) +deriveWrappingKey pass salt = do + params <- readRuntimeKdfParams + let outputLen = fromIntegral (kdfOutputLength params) + memBytes = fromIntegral (kdfMemoryKiB params) * 1024 :: Word64 + (status, key) <- + B.allocRet outputLen $ \out -> + withByteArray pass $ \ppass -> + withSaltPtr salt $ \saltPtr -> + wallet_sodium_argon2id + (coerce out) + (fromIntegral @Int @CULLong outputLen) + (coerce ppass) + (fromIntegral @Int @CULLong $ B.length pass) + saltPtr + (fromIntegral @Word @CULLong $ kdfTimeCost params) + (fromIntegral @Word64 @CSize memBytes) + pure (if status == 0 then Right key else 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_sodium_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) From db74c85f57d387ee40c9e744372342b5e3a32103 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 17:25:39 +0300 Subject: [PATCH 22/33] More type safety for `Tag` --- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 66 +++++++++++++++---- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index b3fff4506..38a564051 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -59,6 +59,13 @@ module Cardano.Crypto.WalletHD.Encrypted ( nonceByteArray, nonceByteString, + -- ** Tag + Tag, + tagSize, + mkTag, + tagByteArray, + tagByteString, + -- * Construction & validation encryptedCreate, encryptedCreateDirectWithTweak, @@ -270,7 +277,6 @@ allocaKeyMaterialBuffer action = mlsbUseAsCPtr keyMaterialBuffer (action . KeyMaterialPtr) type Ciphertext = ByteString -type AuthenticationTag = ByteString type AadContext = ByteString -- --------------------------------------------------------------------------- @@ -311,9 +317,6 @@ productionArgonTimeCost = kdfTimeCost productionKdfParams productionArgonParallelism = kdfParallelism productionKdfParams productionArgonOutputLength = kdfOutputLength productionKdfParams -tagSize :: Int -tagSize = 16 - -- --------------------------------------------------------------------------- -- Random-mode override (for testing) -- --------------------------------------------------------------------------- @@ -458,13 +461,50 @@ decodeNonce = do Nothing -> failDecoder XPrvInvalidNonceLength Just nonce -> pure nonce +------------------------------------------------------------------------------ +-- TAG +------------------------------------------------------------------------------ + +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 + data V2Envelope = V2Envelope { v2Salt :: !Salt , v2Nonce :: !Nonce , v2PublicKey :: !PublicKey , v2ChainCode :: !ChainCode , v2Ciphertext :: !Ciphertext - , v2Tag :: !AuthenticationTag + , v2Tag :: !Tag } deriving (Eq, Show) @@ -472,7 +512,6 @@ data V2Envelope = V2Envelope newtype MasterKeyPtr = MasterKeyPtr (Ptr Word8) newtype SignaturePtr = SignaturePtr (Ptr Word8) newtype PassPhrasePtr = PassPhrasePtr (Ptr Word8) -newtype TagPtr = TagPtr (Ptr Word8) newtype CiphertextPtr = CiphertextPtr (Ptr Word8) newtype WrappingKeyPtr = WrappingKeyPtr (Ptr Word8) @@ -642,8 +681,7 @@ decodeEnvelope = do aad <- decodeBytes ciphertext <- decodeBytes when (BS.length ciphertext /= secretKeySize) (failDecoder XPrvInvalidCiphertextLength) - tag <- decodeBytes - when (BS.length tag /= tagSize) (failDecoder XPrvInvalidTagLength) + tag <- decodeTag (pub, cc) <- either failDecoder pure $ decodeAad aad pure $ V2Envelope @@ -672,7 +710,7 @@ encodeV2Envelope envelope = , encodeNonce (v2Nonce envelope) , encodeBytes (encodeAad (v2PublicKey envelope) (v2ChainCode envelope)) , encodeBytes (v2Ciphertext envelope) - , encodeBytes (v2Tag envelope) + , encodeTag (v2Tag envelope) ] encodeAad :: PublicKey -> ChainCode -> AadContext @@ -776,7 +814,7 @@ decryptKeyMaterialV2 secretKey eKey pass = status <- withSecretKeyPtr secretKey $ \secretKeyPtr -> withByteArray (v2Ciphertext envelope) $ \ct -> - withByteArray (v2Tag envelope) $ \tg -> + withTagPtr (v2Tag envelope) $ \tagPtr -> withByteArray aad $ \ad -> withNoncePtr (v2Nonce envelope) $ \noncePtr -> withByteArray wrappingKey $ \kp -> @@ -784,7 +822,7 @@ decryptKeyMaterialV2 secretKey eKey pass = secretKeyPtr (coerce ct) (fromIntegral @Int @CULLong $ BS.length (v2Ciphertext envelope)) - (coerce tg) + tagPtr ad (fromIntegral @Int @CULLong $ BS.length aad) noncePtr @@ -816,15 +854,15 @@ wrapKeyMaterial pass material = do Right wrappingKey -> do let aad = encodeAad (kmPublicKey material) (kmChainCode material) withSecretKeyPtr (kmSecretKey material) $ \skPtr -> do - ((status, tag), ciphertext) <- + ((tag, status), ciphertext) <- B.allocRet secretKeySize $ \outCipher -> - B.allocRet tagSize $ \outTag -> + fmap (first Tag) $ psbCreateResult $ \outTagPtr -> withByteArray aad $ \ad -> withNoncePtr nonce $ \noncePtr -> withByteArray wrappingKey $ \kp -> wallet_sodium_xchacha20poly1305_encrypt (coerce outCipher) - (coerce outTag) + (TagPtr outTagPtr) skPtr (fromIntegral @Int @CULLong secretKeySize) ad From f05aa5f49f6bc65f5ddd622750abdfa3ab15035a Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 18:05:09 +0300 Subject: [PATCH 23/33] WIP rename plaintext to secret_key --- cardano-crypto-wallet/cbits/encrypted_sign.c | 14 +++++--------- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 2 -- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/cardano-crypto-wallet/cbits/encrypted_sign.c b/cardano-crypto-wallet/cbits/encrypted_sign.c index c98719908..71a67c687 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -373,8 +373,7 @@ int wallet_sodium_argon2id(uint8_t *out, int wallet_sodium_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], @@ -386,14 +385,11 @@ int wallet_sodium_xchacha20poly1305_encrypt( if (ensure_sodium() != 0) { return 1; } - if (plaintext_len != UNENCRYPTED_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, @@ -402,8 +398,8 @@ 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, crypto_aead_xchacha20poly1305_ietf_ABYTES); secure_clear(combined, sizeof(combined)); return 0; } diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 38a564051..ff6ea96fe 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -864,7 +864,6 @@ wrapKeyMaterial pass material = do (coerce outCipher) (TagPtr outTagPtr) skPtr - (fromIntegral @Int @CULLong secretKeySize) ad (fromIntegral @Int @CULLong $ BS.length aad) noncePtr @@ -1119,7 +1118,6 @@ foreign import ccall "wallet_sodium_xchacha20poly1305_encrypt" CiphertextPtr -> TagPtr -> SecretKeyPtr -> - CULLong -> Ptr Word8 -> CULLong -> NoncePtr -> From 627206ed8d02404396849f74dfe1ee3bb1aa9b40 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 18:59:55 +0300 Subject: [PATCH 24/33] Rename Ciphertext to EncSecretKey --- cardano-crypto-wallet/cbits/encrypted_sign.c | 4 +- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 68 ++++++++++++++----- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/cardano-crypto-wallet/cbits/encrypted_sign.c b/cardano-crypto-wallet/cbits/encrypted_sign.c index 71a67c687..45b316750 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -405,7 +405,7 @@ int wallet_sodium_xchacha20poly1305_encrypt( } int wallet_sodium_xchacha20poly1305_decrypt( - uint8_t *plaintext, + uint8_t *secret_to_decrypt, uint8_t const *ciphertext, unsigned long long ciphertext_len, uint8_t const tag[crypto_aead_xchacha20poly1305_ietf_ABYTES], @@ -426,7 +426,7 @@ int wallet_sodium_xchacha20poly1305_decrypt( memcpy(combined, ciphertext, (size_t) ciphertext_len); memcpy(combined + ciphertext_len, tag, crypto_aead_xchacha20poly1305_ietf_ABYTES); if (crypto_aead_xchacha20poly1305_ietf_decrypt( - plaintext, + secret_to_decrypt, &plen, NULL, combined, diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index ff6ea96fe..700dcaf08 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -38,6 +38,12 @@ module Cardano.Crypto.WalletHD.Encrypted ( publicKeyByteArray, publicKeyByteString, + -- ** Encrypted SecretKey + EncSecretKey, + mkEncSecretKey, + encSecretKeyByteArray, + encSecretKeyByteString, + -- ** ChainCode ChainCode, chainCodeSize, @@ -182,7 +188,9 @@ 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 @@ -190,6 +198,37 @@ withSecretKeyPtr (SecretKey secretKey) action = mlsbUseAsCPtr secretKey (action . SecretKeyPtr) {-# INLINE withSecretKeyPtr #-} +-- Encrypted version (same size as decrypted) + +-- | Encrypted version of `SecretKey` +newtype EncSecretKey = EncSecretKey {unEncSecretKey :: PinnedSizedBytes SECRET_KEY_SIZE} + deriving (Eq, Show) +newtype EncSecretKeyPtr = EncSecretKeyPtr (Ptr Word8) + +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 ------------------------------------------------------------------------------ @@ -276,7 +315,6 @@ allocaKeyMaterialBuffer action = mlsbCreate KeyMaterialBuffer $ \(KeyMaterialBuffer keyMaterialBuffer) -> mlsbUseAsCPtr keyMaterialBuffer (action . KeyMaterialPtr) -type Ciphertext = ByteString type AadContext = ByteString -- --------------------------------------------------------------------------- @@ -503,7 +541,7 @@ data V2Envelope = V2Envelope , v2Nonce :: !Nonce , v2PublicKey :: !PublicKey , v2ChainCode :: !ChainCode - , v2Ciphertext :: !Ciphertext + , v2EncSecretKey :: !EncSecretKey , v2Tag :: !Tag } deriving (Eq, Show) @@ -512,7 +550,6 @@ data V2Envelope = V2Envelope newtype MasterKeyPtr = MasterKeyPtr (Ptr Word8) newtype SignaturePtr = SignaturePtr (Ptr Word8) newtype PassPhrasePtr = PassPhrasePtr (Ptr Word8) -newtype CiphertextPtr = CiphertextPtr (Ptr Word8) newtype WrappingKeyPtr = WrappingKeyPtr (Ptr Word8) type CDerivationScheme = CInt @@ -679,8 +716,7 @@ decodeEnvelope = do when (cipherId /= xchacha20poly1305Id) (failDecoder XPrvUnsupportedCipher) nonce <- decodeNonce aad <- decodeBytes - ciphertext <- decodeBytes - when (BS.length ciphertext /= secretKeySize) (failDecoder XPrvInvalidCiphertextLength) + encSecretKey <- decodeEncSecretKey tag <- decodeTag (pub, cc) <- either failDecoder pure $ decodeAad aad pure $ @@ -689,7 +725,7 @@ decodeEnvelope = do , v2Nonce = nonce , v2PublicKey = pub , v2ChainCode = cc - , v2Ciphertext = ciphertext + , v2EncSecretKey = encSecretKey , v2Tag = tag } @@ -709,7 +745,7 @@ encodeV2Envelope envelope = , encodeWord xchacha20poly1305Id , encodeNonce (v2Nonce envelope) , encodeBytes (encodeAad (v2PublicKey envelope) (v2ChainCode envelope)) - , encodeBytes (v2Ciphertext envelope) + , encodeEncSecretKey (v2EncSecretKey envelope) , encodeTag (v2Tag envelope) ] @@ -813,15 +849,15 @@ decryptKeyMaterialV2 secretKey eKey pass = let aad = encodeAad (v2PublicKey envelope) (v2ChainCode envelope) status <- withSecretKeyPtr secretKey $ \secretKeyPtr -> - withByteArray (v2Ciphertext envelope) $ \ct -> + withEncSecretKeyPtr (v2EncSecretKey envelope) $ \encSecretKeyPtr -> withTagPtr (v2Tag envelope) $ \tagPtr -> withByteArray aad $ \ad -> withNoncePtr (v2Nonce envelope) $ \noncePtr -> withByteArray wrappingKey $ \kp -> wallet_sodium_xchacha20poly1305_decrypt secretKeyPtr - (coerce ct) - (fromIntegral @Int @CULLong $ BS.length (v2Ciphertext envelope)) + encSecretKeyPtr + (fromIntegral @Int @CULLong $ secretKeySize) tagPtr ad (fromIntegral @Int @CULLong $ BS.length aad) @@ -854,14 +890,14 @@ wrapKeyMaterial pass material = do Right wrappingKey -> do let aad = encodeAad (kmPublicKey material) (kmChainCode material) withSecretKeyPtr (kmSecretKey material) $ \skPtr -> do - ((tag, status), ciphertext) <- - B.allocRet secretKeySize $ \outCipher -> + (encSecretKey, (tag, status)) <- + fmap (first EncSecretKey) $ psbCreateResult $ \outEncSecretKey -> fmap (first Tag) $ psbCreateResult $ \outTagPtr -> withByteArray aad $ \ad -> withNoncePtr nonce $ \noncePtr -> withByteArray wrappingKey $ \kp -> wallet_sodium_xchacha20poly1305_encrypt - (coerce outCipher) + (coerce outEncSecretKey) (TagPtr outTagPtr) skPtr ad @@ -875,7 +911,7 @@ wrapKeyMaterial pass material = do Right $ EncryptedKey $ encodeV2Envelope $ - V2Envelope salt nonce (kmPublicKey material) (kmChainCode material) ciphertext tag + V2Envelope salt nonce (kmPublicKey material) (kmChainCode material) encSecretKey tag -- | Verify that associated public key matches the secret key in the `KeyMaterial` validateKeyMaterial :: KeyMaterial Unchecked -> IO (Either XPrvError (KeyMaterial Validated)) @@ -1115,7 +1151,7 @@ foreign import ccall "wallet_sodium_argon2id" foreign import ccall "wallet_sodium_xchacha20poly1305_encrypt" wallet_sodium_xchacha20poly1305_encrypt :: - CiphertextPtr -> + EncSecretKeyPtr -> TagPtr -> SecretKeyPtr -> Ptr Word8 -> @@ -1127,7 +1163,7 @@ foreign import ccall "wallet_sodium_xchacha20poly1305_encrypt" foreign import ccall "wallet_sodium_xchacha20poly1305_decrypt" wallet_sodium_xchacha20poly1305_decrypt :: SecretKeyPtr -> - CiphertextPtr -> + EncSecretKeyPtr -> CULLong -> TagPtr -> Ptr Word8 -> From 9f49b8922a96566587d169cf5b9d060a025d01a8 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 19:02:01 +0300 Subject: [PATCH 25/33] Remove another redundant argument to decrypt C function --- cardano-crypto-wallet/cbits/encrypted_sign.c | 12 ++++-------- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 2 -- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/cardano-crypto-wallet/cbits/encrypted_sign.c b/cardano-crypto-wallet/cbits/encrypted_sign.c index 45b316750..df86fa625 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -407,7 +407,6 @@ int wallet_sodium_xchacha20poly1305_encrypt( int wallet_sodium_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, @@ -420,17 +419,14 @@ int wallet_sodium_xchacha20poly1305_decrypt( if (ensure_sodium() != 0) { return 1; } - if (ciphertext_len != UNENCRYPTED_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( 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, @@ -439,5 +435,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 700dcaf08..eb1512fb1 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -857,7 +857,6 @@ decryptKeyMaterialV2 secretKey eKey pass = wallet_sodium_xchacha20poly1305_decrypt secretKeyPtr encSecretKeyPtr - (fromIntegral @Int @CULLong $ secretKeySize) tagPtr ad (fromIntegral @Int @CULLong $ BS.length aad) @@ -1164,7 +1163,6 @@ foreign import ccall "wallet_sodium_xchacha20poly1305_decrypt" wallet_sodium_xchacha20poly1305_decrypt :: SecretKeyPtr -> EncSecretKeyPtr -> - CULLong -> TagPtr -> Ptr Word8 -> CULLong -> From 3c4178136fc1a71b6419a4e61976727bdada86ab Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 23:09:10 +0300 Subject: [PATCH 26/33] Introduce `WrappingKey` and remove redundant parameters --- cardano-crypto-wallet/cbits/encrypted_sign.c | 6 +- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 182 ++++++++++-------- 2 files changed, 101 insertions(+), 87 deletions(-) diff --git a/cardano-crypto-wallet/cbits/encrypted_sign.c b/cardano-crypto-wallet/cbits/encrypted_sign.c index df86fa625..e3ac259d4 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -347,7 +347,6 @@ int wallet_sodium_randombytes(void * const out, size_t const out_len) } int wallet_sodium_argon2id(uint8_t *out, - unsigned long long out_len, uint8_t const *pass, unsigned long long pass_len, uint8_t const salt[crypto_pwhash_SALTBYTES], @@ -357,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, diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index eb1512fb1..05278538e 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -183,6 +183,7 @@ mlsbCreate mkType action = bracket mlsbNewZero mlsbFinalize (action . mkType) -- SECRET_KEY ------------------------------------------------------------------------------ +-- TODO: Derive from: `UNENCRYPTED_KEY_SIZE` type SECRET_KEY_SIZE = 64 secretKeySize :: Int @@ -203,6 +204,7 @@ withSecretKeyPtr (SecretKey secretKey) action = -- | Encrypted version of `SecretKey` newtype EncSecretKey = EncSecretKey {unEncSecretKey :: PinnedSizedBytes SECRET_KEY_SIZE} deriving (Eq, Show) + newtype EncSecretKeyPtr = EncSecretKeyPtr (Ptr Word8) withEncSecretKeyPtr :: EncSecretKey -> (EncSecretKeyPtr -> IO a) -> IO a @@ -334,12 +336,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) @@ -353,7 +354,7 @@ productionArgonMemoryKiB productionArgonMemoryKiB = kdfMemoryKiB productionKdfParams productionArgonTimeCost = kdfTimeCost productionKdfParams productionArgonParallelism = kdfParallelism productionKdfParams -productionArgonOutputLength = kdfOutputLength productionKdfParams +productionArgonOutputLength = fromIntegral @Int @Word wrappingKeySize -- --------------------------------------------------------------------------- -- Random-mode override (for testing) @@ -466,6 +467,7 @@ decodeSalt = do -- NONCE ------------------------------------------------------------------------------ +-- TODO: Derive from `crypto_aead_xchacha20poly1305_ietf_NPUBBYTES` type NONCE_SIZE = 24 nonceSize :: Int @@ -503,6 +505,7 @@ decodeNonce = do -- TAG ------------------------------------------------------------------------------ +-- TODO: Derive from: `crypto_aead_xchacha20poly1305_ietf_ABYTES` type TAG_SIZE = 16 tagSize :: Int @@ -536,6 +539,26 @@ decodeTag = do 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 V2Envelope = V2Envelope { v2Salt :: !Salt , v2Nonce :: !Nonce @@ -550,7 +573,6 @@ data V2Envelope = V2Envelope newtype MasterKeyPtr = MasterKeyPtr (Ptr Word8) newtype SignaturePtr = SignaturePtr (Ptr Word8) newtype PassPhrasePtr = PassPhrasePtr (Ptr Word8) -newtype WrappingKeyPtr = WrappingKeyPtr (Ptr Word8) type CDerivationScheme = CInt @@ -842,37 +864,34 @@ decryptKeyMaterialV2 secretKey eKey pass = case decodeV2Envelope 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) - status <- - withSecretKeyPtr secretKey $ \secretKeyPtr -> - withEncSecretKeyPtr (v2EncSecretKey envelope) $ \encSecretKeyPtr -> - withTagPtr (v2Tag envelope) $ \tagPtr -> - withByteArray aad $ \ad -> - withNoncePtr (v2Nonce envelope) $ \noncePtr -> - withByteArray wrappingKey $ \kp -> - wallet_sodium_xchacha20poly1305_decrypt - secretKeyPtr - encSecretKeyPtr - tagPtr - ad - (fromIntegral @Int @CULLong $ BS.length aad) - noncePtr - (coerce kp) - if status /= 0 - then - pure $ Left XPrvAuthenticationFailed - else - pure $ - Right $ - KeyMaterial - { kmSecretKey = secretKey - , kmPublicKey = v2PublicKey envelope - , kmChainCode = v2ChainCode envelope - } + withWrappingKey pass (v2Salt envelope) $ \wrappingKey -> do + let aad = encodeAad (v2PublicKey envelope) (v2ChainCode envelope) + status <- + withSecretKeyPtr secretKey $ \secretKeyPtr -> + withEncSecretKeyPtr (v2EncSecretKey envelope) $ \encSecretKeyPtr -> + withTagPtr (v2Tag envelope) $ \tagPtr -> + withByteArray aad $ \ad -> + withNoncePtr (v2Nonce envelope) $ \noncePtr -> + withWrappingKeyPtr wrappingKey $ \wrappingKeyPtr -> + wallet_sodium_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 = v2PublicKey envelope + , kmChainCode = v2ChainCode envelope + } wrapKeyMaterial :: ByteArrayAccess passphrase => @@ -883,34 +902,31 @@ wrapKeyMaterial pass material = do 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) - withSecretKeyPtr (kmSecretKey material) $ \skPtr -> do - (encSecretKey, (tag, status)) <- - fmap (first EncSecretKey) $ psbCreateResult $ \outEncSecretKey -> - fmap (first Tag) $ psbCreateResult $ \outTagPtr -> - withByteArray aad $ \ad -> - withNoncePtr nonce $ \noncePtr -> - withByteArray wrappingKey $ \kp -> - wallet_sodium_xchacha20poly1305_encrypt - (coerce outEncSecretKey) - (TagPtr outTagPtr) - skPtr - ad - (fromIntegral @Int @CULLong $ BS.length aad) - noncePtr - (coerce kp) - if status /= 0 - then pure (Left XPrvInternalError) - else - pure $ - Right $ - EncryptedKey $ - encodeV2Envelope $ - V2Envelope salt nonce (kmPublicKey material) (kmChainCode material) encSecretKey tag + withWrappingKey pass salt $ \wrappingKey -> do + let aad = encodeAad (kmPublicKey material) (kmChainCode material) + withSecretKeyPtr (kmSecretKey material) $ \skPtr -> do + (encSecretKey, (tag, status)) <- + fmap (first EncSecretKey) $ psbCreateResult $ \outEncSecretKey -> + fmap (first Tag) $ psbCreateResult $ \outTagPtr -> + withByteArray aad $ \ad -> + withNoncePtr nonce $ \noncePtr -> + withWrappingKeyPtr wrappingKey $ \wrappingKeyPtr -> + wallet_sodium_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 $ + encodeV2Envelope $ + V2Envelope salt nonce (kmPublicKey material) (kmChainCode material) encSecretKey tag -- | Verify that associated public key matches the secret key in the `KeyMaterial` validateKeyMaterial :: KeyMaterial Unchecked -> IO (Either XPrvError (KeyMaterial Validated)) @@ -1021,26 +1037,29 @@ legacyDerivePrivate dscheme parent childIndex action = -- Internal: KDF and random bytes -- --------------------------------------------------------------------------- -deriveWrappingKey :: +withWrappingKey :: ByteArrayAccess passphrase => - passphrase -> Salt -> IO (Either XPrvError B.ScrubbedBytes) -deriveWrappingKey pass salt = do + passphrase -> Salt -> (WrappingKey -> IO (Either XPrvError a)) -> IO (Either XPrvError a) +withWrappingKey pass salt action = do params <- readRuntimeKdfParams - let outputLen = fromIntegral (kdfOutputLength params) - memBytes = fromIntegral (kdfMemoryKiB params) * 1024 :: Word64 - (status, key) <- - B.allocRet outputLen $ \out -> + let memBytes = fromIntegral (kdfMemoryKiB params) * 1024 :: Word64 + mlsbCreate WrappingKey $ \wrappingKey -> + withWrappingKeyPtr wrappingKey $ \outWrappingKeyPtr -> withByteArray pass $ \ppass -> - withSaltPtr salt $ \saltPtr -> - wallet_sodium_argon2id - (coerce out) - (fromIntegral @Int @CULLong outputLen) - (coerce ppass) - (fromIntegral @Int @CULLong $ B.length pass) - saltPtr - (fromIntegral @Word @CULLong $ kdfTimeCost params) - (fromIntegral @Word64 @CSize memBytes) - pure (if status == 0 then Right key else Left XPrvInternalError) + withSaltPtr salt $ \saltPtr -> do + status <- + wallet_sodium_argon2id + outWrappingKeyPtr + (coerce ppass) + (fromIntegral @Int @CULLong $ B.length pass) + saltPtr + (fromIntegral @Word @CULLong $ kdfTimeCost params) + (fromIntegral @Word64 @CSize memBytes) + if status == 0 + then + action wrappingKey + else + pure $ Left XPrvInternalError randomBytesIO :: KnownNat n => IO (Either XPrvError (PinnedSizedBytes n)) randomBytesIO = do @@ -1140,7 +1159,6 @@ foreign import ccall "wallet_sodium_randombytes" foreign import ccall "wallet_sodium_argon2id" wallet_sodium_argon2id :: WrappingKeyPtr -> - CULLong -> PassPhrasePtr -> CULLong -> SaltPtr -> From 1387291e3b945eae183d66c422bb8283bcffc474 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 31 May 2026 23:27:14 +0300 Subject: [PATCH 27/33] Rename `Envelope` and other minor cleanup --- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 97 ++++++++++--------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 05278538e..36f2eddc3 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -559,13 +559,13 @@ withWrappingKeyPtr (WrappingKey wrappingKey) action = mlsbUseAsCPtr wrappingKey (action . WrappingKeyPtr) {-# INLINE withWrappingKeyPtr #-} -data V2Envelope = V2Envelope - { v2Salt :: !Salt - , v2Nonce :: !Nonce - , v2PublicKey :: !PublicKey - , v2ChainCode :: !ChainCode - , v2EncSecretKey :: !EncSecretKey - , v2Tag :: !Tag +data Envelope = Envelope + { eSalt :: !Salt + , eNonce :: !Nonce + , ePublicKey :: !PublicKey + , eChainCode :: !ChainCode + , eEncSecretKey :: !EncSecretKey + , eTag :: !Tag } deriving (Eq, Show) @@ -595,7 +595,7 @@ validateSerializedKey :: EncryptedKey -> Either XPrvError () validateSerializedKey eKey = case encryptedKeyFormat eKey of LegacyV1 -> Right () - EnvelopeV2 -> () <$ decodeV2Envelope eKey + EnvelopeV2 -> () <$ decodeEncryptedKeyV2 eKey encryptedKeyFormat :: EncryptedKey -> XPrvFormat encryptedKeyFormat (EncryptedKey bs) @@ -690,7 +690,7 @@ encryptedPublic :: HasCallStack => EncryptedKey -> PublicKey encryptedPublic eKey@(EncryptedKey eKeyBytes) = case encryptedKeyFormat eKey of LegacyV1 -> errorFail $ mkPublicKey $ sub secretKeySize publicKeySize eKeyBytes - EnvelopeV2 -> either (const badEnvelope) v2PublicKey (decodeV2Envelope eKey) + EnvelopeV2 -> either (const badEnvelope) ePublicKey (decodeEncryptedKeyV2 eKey) where badEnvelope = error "encryptedPublic: invalid v2 envelope" @@ -699,7 +699,7 @@ encryptedChainCode eKey@(EncryptedKey eKeyBytes) = case encryptedKeyFormat eKey of LegacyV1 -> errorFail $ mkChainCode $ sub (secretKeySize + publicKeySize) chainCodeSize eKeyBytes - EnvelopeV2 -> either (const badEnvelope) v2ChainCode (decodeV2Envelope eKey) + EnvelopeV2 -> either (const badEnvelope) eChainCode (decodeEncryptedKeyV2 eKey) where badEnvelope = error "encryptedChainCode: invalid v2 envelope" @@ -707,14 +707,14 @@ encryptedChainCode eKey@(EncryptedKey eKeyBytes) = -- Internal: CBOR V2 envelope codec -- --------------------------------------------------------------------------- -decodeV2Envelope :: EncryptedKey -> Either XPrvError V2Envelope -decodeV2Envelope (EncryptedKey eKeyBytes) = +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 @@ -742,17 +742,17 @@ decodeEnvelope = do tag <- decodeTag (pub, cc) <- either failDecoder pure $ decodeAad aad pure $ - V2Envelope - { v2Salt = salt - , v2Nonce = nonce - , v2PublicKey = pub - , v2ChainCode = cc - , v2EncSecretKey = encSecretKey - , 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 @@ -763,12 +763,12 @@ encodeV2Envelope envelope = , encodeWord productionArgonTimeCost , encodeWord productionArgonParallelism , encodeWord productionArgonOutputLength - , encodeSalt (v2Salt envelope) + , encodeSalt (eSalt envelope) , encodeWord xchacha20poly1305Id - , encodeNonce (v2Nonce envelope) - , encodeBytes (encodeAad (v2PublicKey envelope) (v2ChainCode envelope)) - , encodeEncSecretKey (v2EncSecretKey envelope) - , encodeTag (v2Tag envelope) + , encodeNonce (eNonce envelope) + , encodeBytes (encodeAad (ePublicKey envelope) (eChainCode envelope)) + , encodeEncSecretKey (eEncSecretKey envelope) + , encodeTag (eTag envelope) ] encodeAad :: PublicKey -> ChainCode -> AadContext @@ -861,17 +861,17 @@ decryptKeyMaterialV2 :: passphrase -> IO (Either XPrvError (KeyMaterial Unchecked)) decryptKeyMaterialV2 secretKey eKey pass = - case decodeV2Envelope eKey of + case decodeEncryptedKeyV2 eKey of Left err -> pure (Left err) Right envelope -> do - withWrappingKey pass (v2Salt envelope) $ \wrappingKey -> do - let aad = encodeAad (v2PublicKey envelope) (v2ChainCode envelope) + withWrappingKey pass (eSalt envelope) $ \wrappingKey -> do + let aad = encodeAad (ePublicKey envelope) (eChainCode envelope) status <- withSecretKeyPtr secretKey $ \secretKeyPtr -> - withEncSecretKeyPtr (v2EncSecretKey envelope) $ \encSecretKeyPtr -> - withTagPtr (v2Tag envelope) $ \tagPtr -> + withEncSecretKeyPtr (eEncSecretKey envelope) $ \encSecretKeyPtr -> + withTagPtr (eTag envelope) $ \tagPtr -> withByteArray aad $ \ad -> - withNoncePtr (v2Nonce envelope) $ \noncePtr -> + withNoncePtr (eNonce envelope) $ \noncePtr -> withWrappingKeyPtr wrappingKey $ \wrappingKeyPtr -> wallet_sodium_xchacha20poly1305_decrypt secretKeyPtr @@ -889,22 +889,22 @@ decryptKeyMaterialV2 secretKey eKey pass = Right $ KeyMaterial { kmSecretKey = secretKey - , kmPublicKey = v2PublicKey envelope - , kmChainCode = v2ChainCode envelope + , kmPublicKey = ePublicKey envelope + , kmChainCode = eChainCode envelope } wrapKeyMaterial :: ByteArrayAccess passphrase => passphrase -> KeyMaterial Validated -> IO (Either XPrvError EncryptedKey) -wrapKeyMaterial pass material = do +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 (salt, nonce) -> do withWrappingKey pass salt $ \wrappingKey -> do - let aad = encodeAad (kmPublicKey material) (kmChainCode material) - withSecretKeyPtr (kmSecretKey material) $ \skPtr -> do + let aad = encodeAad kmPublicKey kmChainCode + withSecretKeyPtr kmSecretKey $ \skPtr -> do (encSecretKey, (tag, status)) <- fmap (first EncSecretKey) $ psbCreateResult $ \outEncSecretKey -> fmap (first Tag) $ psbCreateResult $ \outTagPtr -> @@ -925,8 +925,15 @@ wrapKeyMaterial pass material = do pure $ Right $ EncryptedKey $ - encodeV2Envelope $ - V2Envelope salt nonce (kmPublicKey material) (kmChainCode material) encSecretKey tag + 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)) @@ -1006,7 +1013,7 @@ legacyMaterialFromSecret sec cc action = withNewKeyMaterial XPrvInvalidSecretKey action $ \outPtr -> withByteArray sec $ \psec -> withByteArray cc $ \pcc -> - wallet_from_secret (coerce psec) (coerce pcc) (coerce outPtr) + wallet_from_secret (coerce psec) (coerce pcc) outPtr legacyMaterialFromMasterKey :: ByteArrayAccess secret => @@ -1016,7 +1023,7 @@ legacyMaterialFromMasterKey :: legacyMaterialFromMasterKey sec action = withNewKeyMaterial XPrvInvalidSecretKey action $ \outPtr -> withByteArray sec $ \psec -> - wallet_new_from_mkg (MasterKeyPtr psec) (coerce outPtr) + wallet_new_from_mkg (MasterKeyPtr psec) outPtr legacyDerivePrivate :: DerivationScheme -> @@ -1027,11 +1034,7 @@ legacyDerivePrivate :: legacyDerivePrivate dscheme parent childIndex action = withKeyMaterialPtr parent $ \inPtr -> withNewKeyMaterial XPrvInternalError action $ \outPtr -> - wallet_derive_private - inPtr - childIndex - (coerce outPtr) - (dschemeToC dscheme) + wallet_derive_private inPtr childIndex outPtr (dschemeToC dscheme) -- --------------------------------------------------------------------------- -- Internal: KDF and random bytes From 7e1d046e604832a657a4474b6fb79c0850a1b2da Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Mon, 1 Jun 2026 16:24:23 +0300 Subject: [PATCH 28/33] Expose safe parts of the `Envelope` --- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 36f2eddc3..4287ead0e 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -72,12 +72,17 @@ module Cardano.Crypto.WalletHD.Encrypted ( tagByteArray, tagByteString, + -- ** Envelope + Envelope (eSalt, eNonce, ePublicKey, eChainCode), + encodeEnvelope, + decodeEnvelope, + -- * Construction & validation encryptedCreate, encryptedCreateDirectWithTweak, mkEncryptedKey, - encryptedKey, unEncryptedKey, + encryptedKey, encryptedKeyFormat, -- * Passphrase operations @@ -317,8 +322,6 @@ allocaKeyMaterialBuffer action = mlsbCreate KeyMaterialBuffer $ \(KeyMaterialBuffer keyMaterialBuffer) -> mlsbUseAsCPtr keyMaterialBuffer (action . KeyMaterialPtr) -type AadContext = ByteString - -- --------------------------------------------------------------------------- -- V2 envelope constants -- --------------------------------------------------------------------------- @@ -612,7 +615,6 @@ encryptedCreate sec pass cc | B.length sec /= 32 = pure (Left XPrvInvalidSecretKey) | B.length cc /= chainCodeSize = pure (Left XPrvInvalidChainCode) | otherwise = legacyMaterialFromSecret sec cc (wrapKeyMaterial pass) -{-# NOINLINE encryptedCreate #-} encryptedCreateDirectWithTweak :: (ByteArrayAccess passphrase, ByteArrayAccess secret) => @@ -620,7 +622,6 @@ encryptedCreateDirectWithTweak :: encryptedCreateDirectWithTweak sec pass | B.length sec /= 96 = pure (Left XPrvInvalidSecretKey) | otherwise = legacyMaterialFromMasterKey sec (wrapKeyMaterial pass) -{-# NOINLINE encryptedCreateDirectWithTweak #-} encryptedValidatePassphrase :: ByteArrayAccess passphrase => @@ -771,7 +772,7 @@ encodeEnvelope envelope = , encodeTag (eTag envelope) ] -encodeAad :: PublicKey -> ChainCode -> AadContext +encodeAad :: PublicKey -> ChainCode -> ByteString encodeAad publicKey cc = CBOR.toStrictByteString $ mconcat @@ -790,7 +791,7 @@ encodeAad publicKey cc = , 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) From 8f3169d30c4e9103e890ea4769c084b23b30f01c Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Thu, 4 Jun 2026 11:09:29 +0300 Subject: [PATCH 29/33] Consistent and unique naming for C functions and FFI --- cardano-crypto-wallet/cbits/ed25519/ed25519.c | 2 +- cardano-crypto-wallet/cbits/ed25519/ed25519.h | 12 +++---- cardano-crypto-wallet/cbits/encrypted_sign.c | 36 +++++++++---------- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 36 +++++++++---------- .../Test/Cardano/Crypto/Wallet/SignSpec.hs | 6 ++-- 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/cardano-crypto-wallet/cbits/ed25519/ed25519.c b/cardano-crypto-wallet/cbits/ed25519/ed25519.c index ec07ec144..0e508909f 100644 --- a/cardano-crypto-wallet/cbits/ed25519/ed25519.c +++ b/cardano-crypto-wallet/cbits/ed25519/ed25519.c @@ -5,7 +5,7 @@ */ -#define ED25519_FN(fn) cardano_crypto_##fn +#define ED25519_FN(fn) cardano_crypto_wallet_##fn #include "ed25519-donna.h" #include "ed25519.h" diff --git a/cardano-crypto-wallet/cbits/ed25519/ed25519.h b/cardano-crypto-wallet/cbits/ed25519/ed25519.h index 3f47269d5..6912c2172 100644 --- a/cardano-crypto-wallet/cbits/ed25519/ed25519.h +++ b/cardano-crypto-wallet/cbits/ed25519/ed25519.h @@ -12,12 +12,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 cardano_crypto_ed25519_publickey(const ed25519_secret_key sk, ed25519_public_key pk); -int cardano_crypto_ed25519_sign_open(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); -void cardano_crypto_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 cardano_crypto_ed25519_scalar_add (const ed25519_secret_key sk1, const ed25519_secret_key sk2, ed25519_secret_key res); -int cardano_crypto_ed25519_point_add (const ed25519_public_key pk1, const ed25519_public_key pk2, ed25519_public_key res); -int cardano_crypto_ed25519_extend (const ed25519_unextended_secret_key seed, ed25519_secret_key secret); +void cardano_crypto_wallet_ed25519_publickey(const ed25519_secret_key sk, ed25519_public_key pk); +int cardano_crypto_wallet_ed25519_sign_open(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); +void cardano_crypto_wallet_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 cardano_crypto_wallet_ed25519_scalar_add (const ed25519_secret_key sk1, const ed25519_secret_key sk2, ed25519_secret_key res); +int cardano_crypto_wallet_ed25519_point_add (const ed25519_public_key pk1, const ed25519_public_key pk2, ed25519_public_key res); +int cardano_crypto_wallet_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 e3ac259d4..46c2ab27b 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -42,19 +42,19 @@ static void wallet_initialize key_material *out) { ed25519_public_key pub_key; - cardano_crypto_ed25519_publickey(secret_key, pub_key); + cardano_crypto_wallet_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_from_secret +int cardano_crypto_wallet_from_secret (const uint8_t seed[SECRET_KEY_SEED_SIZE], const uint8_t cc[CHAIN_CODE_SIZE], key_material *out) { ed25519_secret_key secret_key; - if (cardano_crypto_ed25519_extend(seed, secret_key)) { + if (cardano_crypto_wallet_ed25519_extend(seed, secret_key)) { secure_clear(secret_key, sizeof(secret_key)); return 1; } @@ -63,7 +63,7 @@ int cardano_wallet_from_secret return 0; } -int cardano_wallet_new_from_mkg +int cardano_crypto_wallet_new_from_mkg (const uint8_t master_key[96], key_material *out) { @@ -79,13 +79,13 @@ int cardano_wallet_new_from_mkg /* Validate that the supplied public key matches the secret key. * Returns 0 on success (keys consistent), 1 on mismatch. */ -int cardano_wallet_validate +int cardano_crypto_wallet_validate (const uint8_t skey[UNENCRYPTED_KEY_SIZE], const uint8_t pkey[PUBLIC_KEY_SIZE]) { ed25519_public_key pub_key; - cardano_crypto_ed25519_publickey(skey, pub_key); + cardano_crypto_wallet_ed25519_publickey(skey, pub_key); if (sodium_memcmp(pub_key, pkey, PUBLIC_KEY_SIZE) != 0) { secure_clear(pub_key, sizeof(pub_key)); return 1; @@ -94,14 +94,14 @@ int cardano_wallet_validate return 0; } -int cardano_wallet_sign +int cardano_crypto_wallet_sign (key_material const *in, uint8_t const *data, uint32_t const data_len, ed25519_signature signature) { ed25519_public_key pub_key; - cardano_crypto_ed25519_publickey(in->skey, pub_key); - cardano_crypto_ed25519_sign(data, data_len, in->cc, CHAIN_CODE_SIZE, in->skey, pub_key, signature); + cardano_crypto_wallet_ed25519_publickey(in->skey, pub_key); + cardano_crypto_wallet_ed25519_sign(data, data_len, in->cc, CHAIN_CODE_SIZE, in->skey, pub_key, signature); secure_clear(pub_key, sizeof(pub_key)); return 0; } @@ -201,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); - cardano_crypto_ed25519_scalar_add(zl8, priv_key, res_key); + cardano_crypto_wallet_ed25519_scalar_add(zl8, priv_key, res_key); break; case DERIVATION_V2: multiply8_v2(zl8, z, 28); @@ -237,11 +237,11 @@ static void add_left_public(uint8_t *out, uint8_t *z, uint8_t *in, derivation_sc break; } - cardano_crypto_ed25519_publickey(zl8, pub_zl8); - cardano_crypto_ed25519_point_add(pub_zl8, in, out); + cardano_crypto_wallet_ed25519_publickey(zl8, pub_zl8); + cardano_crypto_wallet_ed25519_point_add(pub_zl8, in, out); } -int cardano_wallet_derive_private +int cardano_crypto_wallet_derive_private (key_material const *in, uint32_t index, key_material *out, @@ -296,7 +296,7 @@ int cardano_wallet_derive_private return 0; } -int cardano_wallet_derive_public +int cardano_crypto_wallet_derive_public (uint8_t *pub_in, uint8_t *cc_in, uint32_t index, @@ -337,7 +337,7 @@ int cardano_wallet_derive_public return 0; } -int wallet_sodium_randombytes(void * const out, size_t const out_len) +int cardano_crypto_wallet_randombytes(void * const out, size_t const out_len) { if (ensure_sodium() != 0) { return 1; @@ -346,7 +346,7 @@ int wallet_sodium_randombytes(void * const out, size_t const out_len) return 0; } -int wallet_sodium_argon2id(uint8_t *out, +int cardano_crypto_wallet_argon2id(uint8_t *out, uint8_t const *pass, unsigned long long pass_len, uint8_t const salt[crypto_pwhash_SALTBYTES], @@ -366,7 +366,7 @@ int wallet_sodium_argon2id(uint8_t *out, crypto_pwhash_ALG_ARGON2ID13); } -int wallet_sodium_xchacha20poly1305_encrypt( +int cardano_crypto_wallet_xchacha20poly1305_encrypt( uint8_t *ciphertext, uint8_t tag[crypto_aead_xchacha20poly1305_ietf_ABYTES], uint8_t const secret_to_encrypt[UNENCRYPTED_KEY_SIZE], @@ -400,7 +400,7 @@ int wallet_sodium_xchacha20poly1305_encrypt( return 0; } -int wallet_sodium_xchacha20poly1305_decrypt( +int cardano_crypto_wallet_xchacha20poly1305_decrypt( uint8_t *secret_to_decrypt, uint8_t const *ciphertext, uint8_t const tag[crypto_aead_xchacha20poly1305_ietf_ABYTES], diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 4287ead0e..75d7c0f25 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -874,7 +874,7 @@ decryptKeyMaterialV2 secretKey eKey pass = withByteArray aad $ \ad -> withNoncePtr (eNonce envelope) $ \noncePtr -> withWrappingKeyPtr wrappingKey $ \wrappingKeyPtr -> - wallet_sodium_xchacha20poly1305_decrypt + wallet_xchacha20poly1305_decrypt secretKeyPtr encSecretKeyPtr tagPtr @@ -912,7 +912,7 @@ wrapKeyMaterial pass KeyMaterial {kmSecretKey, kmPublicKey, kmChainCode} = do withByteArray aad $ \ad -> withNoncePtr nonce $ \noncePtr -> withWrappingKeyPtr wrappingKey $ \wrappingKeyPtr -> - wallet_sodium_xchacha20poly1305_encrypt + wallet_xchacha20poly1305_encrypt (EncSecretKeyPtr outEncSecretKey) (TagPtr outTagPtr) skPtr @@ -1052,7 +1052,7 @@ withWrappingKey pass salt action = do withByteArray pass $ \ppass -> withSaltPtr salt $ \saltPtr -> do status <- - wallet_sodium_argon2id + wallet_argon2id outWrappingKeyPtr (coerce ppass) (fromIntegral @Int @CULLong $ B.length pass) @@ -1070,7 +1070,7 @@ randomBytesIO = do mode <- readIORef randomModeRef case mode of SystemRandom -> do - (bytes, status) <- psbCreateResultLen wallet_sodium_randombytes + (bytes, status) <- psbCreateResultLen wallet_randombytes pure $ if status == 0 then Right bytes else Left XPrvInternalError DeterministicRandom counter -> do let @@ -1112,26 +1112,26 @@ failDecoder = fail . show -- FFI declarations -- --------------------------------------------------------------------------- -foreign import ccall "cardano_wallet_from_secret" +foreign import ccall "cardano_crypto_wallet_from_secret" wallet_from_secret :: SecretKeyPtr -> ChainCodePtr -> KeyMaterialPtr -> IO CInt -foreign import ccall "cardano_wallet_new_from_mkg" +foreign import ccall "cardano_crypto_wallet_new_from_mkg" wallet_new_from_mkg :: MasterKeyPtr -> KeyMaterialPtr -> IO CInt -foreign import ccall "cardano_wallet_validate" +foreign import ccall "cardano_crypto_wallet_validate" wallet_validate :: SecretKeyPtr -> PublicKeyPtr -> IO CInt -foreign import ccall "cardano_wallet_sign" +foreign import ccall "cardano_crypto_wallet_sign" wallet_sign :: KeyMaterialPtr -> Ptr Word8 -> @@ -1139,7 +1139,7 @@ foreign import ccall "cardano_wallet_sign" SignaturePtr -> IO CInt -foreign import ccall "cardano_wallet_derive_private" +foreign import ccall "cardano_crypto_wallet_derive_private" wallet_derive_private :: KeyMaterialPtr -> DerivationIndex -> @@ -1147,7 +1147,7 @@ foreign import ccall "cardano_wallet_derive_private" CDerivationScheme -> IO CInt -foreign import ccall "cardano_wallet_derive_public" +foreign import ccall "cardano_crypto_wallet_derive_public" wallet_derive_public :: PublicKeyPtr -> ChainCodePtr -> @@ -1157,11 +1157,11 @@ foreign import ccall "cardano_wallet_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 -> PassPhrasePtr -> CULLong -> @@ -1170,8 +1170,8 @@ foreign import ccall "wallet_sodium_argon2id" CSize -> IO CInt -foreign import ccall "wallet_sodium_xchacha20poly1305_encrypt" - wallet_sodium_xchacha20poly1305_encrypt :: +foreign import ccall "cardano_crypto_wallet_xchacha20poly1305_encrypt" + wallet_xchacha20poly1305_encrypt :: EncSecretKeyPtr -> TagPtr -> SecretKeyPtr -> @@ -1181,8 +1181,8 @@ foreign import ccall "wallet_sodium_xchacha20poly1305_encrypt" 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 -> EncSecretKeyPtr -> TagPtr -> 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 91467c2ea..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,8 +11,8 @@ import Test.Hspec import Cardano.Crypto.WalletHD.Encrypted -foreign import ccall "cardano_crypto_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 -> @@ -25,7 +25,7 @@ verifySignature publicKey msg (Signature sig) = unsafePerformIO $ 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) From 55f8c528d85c44a4f150442e332057dc2f1ff8c6 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Thu, 4 Jun 2026 12:06:59 +0300 Subject: [PATCH 30/33] Switch to using consistent `CCW` definition everywhere --- .../cbits/ed25519/ed25519-donna-batchverify.h | 8 ++--- .../cbits/ed25519/ed25519-randombytes.h | 2 +- cardano-crypto-wallet/cbits/ed25519/ed25519.c | 14 ++++---- cardano-crypto-wallet/cbits/ed25519/ed25519.h | 14 ++++---- cardano-crypto-wallet/cbits/encrypted_sign.c | 36 +++++++++---------- 5 files changed, 37 insertions(+), 37 deletions(-) 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 0e508909f..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) cardano_crypto_wallet_##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 6912c2172..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 cardano_crypto_wallet_ed25519_publickey(const ed25519_secret_key sk, ed25519_public_key pk); -int cardano_crypto_wallet_ed25519_sign_open(const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS); -void cardano_crypto_wallet_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 cardano_crypto_wallet_ed25519_scalar_add (const ed25519_secret_key sk1, const ed25519_secret_key sk2, ed25519_secret_key res); -int cardano_crypto_wallet_ed25519_point_add (const ed25519_public_key pk1, const ed25519_public_key pk2, ed25519_public_key res); -int cardano_crypto_wallet_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 46c2ab27b..b023eb4d7 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -42,19 +42,19 @@ static void wallet_initialize key_material *out) { ed25519_public_key pub_key; - cardano_crypto_wallet_ed25519_publickey(secret_key, pub_key); + 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_crypto_wallet_from_secret +int CCW_FN (from_secret) (const uint8_t seed[SECRET_KEY_SEED_SIZE], const uint8_t cc[CHAIN_CODE_SIZE], key_material *out) { ed25519_secret_key secret_key; - if (cardano_crypto_wallet_ed25519_extend(seed, secret_key)) { + if (CCW_FN (ed25519_extend) (seed, secret_key)) { secure_clear(secret_key, sizeof(secret_key)); return 1; } @@ -63,7 +63,7 @@ int cardano_crypto_wallet_from_secret return 0; } -int cardano_crypto_wallet_new_from_mkg +int CCW_FN (new_from_mkg) (const uint8_t master_key[96], key_material *out) { @@ -79,13 +79,13 @@ int cardano_crypto_wallet_new_from_mkg /* Validate that the supplied public key matches the secret key. * Returns 0 on success (keys consistent), 1 on mismatch. */ -int cardano_crypto_wallet_validate +int CCW_FN (validate) (const uint8_t skey[UNENCRYPTED_KEY_SIZE], const uint8_t pkey[PUBLIC_KEY_SIZE]) { ed25519_public_key pub_key; - cardano_crypto_wallet_ed25519_publickey(skey, 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; @@ -94,14 +94,14 @@ int cardano_crypto_wallet_validate return 0; } -int cardano_crypto_wallet_sign +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; - cardano_crypto_wallet_ed25519_publickey(in->skey, pub_key); - cardano_crypto_wallet_ed25519_sign(data, data_len, in->cc, CHAIN_CODE_SIZE, in->skey, 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; } @@ -201,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); - cardano_crypto_wallet_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); @@ -237,11 +237,11 @@ static void add_left_public(uint8_t *out, uint8_t *z, uint8_t *in, derivation_sc break; } - cardano_crypto_wallet_ed25519_publickey(zl8, pub_zl8); - cardano_crypto_wallet_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_crypto_wallet_derive_private +int CCW_FN (derive_private) (key_material const *in, uint32_t index, key_material *out, @@ -296,7 +296,7 @@ int cardano_crypto_wallet_derive_private return 0; } -int cardano_crypto_wallet_derive_public +int CCW_FN (derive_public) (uint8_t *pub_in, uint8_t *cc_in, uint32_t index, @@ -337,7 +337,7 @@ int cardano_crypto_wallet_derive_public return 0; } -int cardano_crypto_wallet_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; @@ -346,7 +346,7 @@ int cardano_crypto_wallet_randombytes(void * const out, size_t const out_len) return 0; } -int cardano_crypto_wallet_argon2id(uint8_t *out, +int CCW_FN (argon2id) (uint8_t *out, uint8_t const *pass, unsigned long long pass_len, uint8_t const salt[crypto_pwhash_SALTBYTES], @@ -366,7 +366,7 @@ int cardano_crypto_wallet_argon2id(uint8_t *out, crypto_pwhash_ALG_ARGON2ID13); } -int cardano_crypto_wallet_xchacha20poly1305_encrypt( +int CCW_FN (xchacha20poly1305_encrypt) ( uint8_t *ciphertext, uint8_t tag[crypto_aead_xchacha20poly1305_ietf_ABYTES], uint8_t const secret_to_encrypt[UNENCRYPTED_KEY_SIZE], @@ -400,7 +400,7 @@ int cardano_crypto_wallet_xchacha20poly1305_encrypt( return 0; } -int cardano_crypto_wallet_xchacha20poly1305_decrypt( +int CCW_FN (xchacha20poly1305_decrypt) ( uint8_t *secret_to_decrypt, uint8_t const *ciphertext, uint8_t const tag[crypto_aead_xchacha20poly1305_ietf_ABYTES], From 7587f8678b9989d3852437795f59d6e594a260e7 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Thu, 4 Jun 2026 12:15:48 +0300 Subject: [PATCH 31/33] TODO: confirm. Ensure more bytes than necessary is not copied over. We also need to make sure that tag is fully set to zero, since it doesn't llook like the full overwrite is guaranteed. --- cardano-crypto-wallet/cbits/encrypted_sign.c | 2 +- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cardano-crypto-wallet/cbits/encrypted_sign.c b/cardano-crypto-wallet/cbits/encrypted_sign.c index b023eb4d7..300e1db9e 100644 --- a/cardano-crypto-wallet/cbits/encrypted_sign.c +++ b/cardano-crypto-wallet/cbits/encrypted_sign.c @@ -395,7 +395,7 @@ int CCW_FN (xchacha20poly1305_encrypt) ( return 1; } memcpy(ciphertext, combined, (size_t) UNENCRYPTED_KEY_SIZE); - memcpy(tag, combined + UNENCRYPTED_KEY_SIZE, crypto_aead_xchacha20poly1305_ietf_ABYTES); + memcpy(tag, combined + UNENCRYPTED_KEY_SIZE, clen - UNENCRYPTED_KEY_SIZE); secure_clear(combined, sizeof(combined)); return 0; } diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 75d7c0f25..58c191a63 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -103,6 +103,7 @@ module Cardano.Crypto.WalletHD.Encrypted ( withDeterministicRandomnessForTesting, ) where +import Cardano.Crypto.Libsodium.Memory (zeroMem) import Cardano.Crypto.PinnedSizedBytes ( PinnedSizedBytes, psbCreate, @@ -911,7 +912,8 @@ wrapKeyMaterial pass KeyMaterial {kmSecretKey, kmPublicKey, kmChainCode} = do fmap (first Tag) $ psbCreateResult $ \outTagPtr -> withByteArray aad $ \ad -> withNoncePtr nonce $ \noncePtr -> - withWrappingKeyPtr wrappingKey $ \wrappingKeyPtr -> + withWrappingKeyPtr wrappingKey $ \wrappingKeyPtr -> do + zeroMem outTagPtr (fromIntegral @Int @CSize tagSize) wallet_xchacha20poly1305_encrypt (EncSecretKeyPtr outEncSecretKey) (TagPtr outTagPtr) From 599cbb629002cac688c3e6074b12dcff89152ab4 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Thu, 4 Jun 2026 12:26:22 +0300 Subject: [PATCH 32/33] Stop writing zeros into memory that is guaranteed to be overwritten --- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 58c191a63..31a0f09da 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -147,7 +147,7 @@ import Cardano.Binary (toCBOR) import Cardano.Crypto.Libsodium.MLockedBytes ( MLockedSizedBytes, mlsbFinalize, - mlsbNewZero, + mlsbNew, mlsbUseAsCPtr, ) import Codec.CBOR.Decoding ( @@ -183,7 +183,7 @@ signatureSize :: Int signatureSize = 64 mlsbCreate :: KnownNat n => (MLockedSizedBytes n -> b) -> (b -> IO c) -> IO c -mlsbCreate mkType action = bracket mlsbNewZero mlsbFinalize (action . mkType) +mlsbCreate mkType action = bracket mlsbNew mlsbFinalize (action . mkType) ------------------------------------------------------------------------------ -- SECRET_KEY From 610e24137eef96e6f43e866bce7b212cb409ada0 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Thu, 4 Jun 2026 13:02:22 +0300 Subject: [PATCH 33/33] Ensure sizes match up --- .../src/Cardano/Crypto/WalletHD/Encrypted.hs | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs index 31a0f09da..f7f4f04cf 100644 --- a/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs +++ b/cardano-crypto-wallet/src/Cardano/Crypto/WalletHD/Encrypted.hs @@ -40,6 +40,7 @@ module Cardano.Crypto.WalletHD.Encrypted ( -- ** Encrypted SecretKey EncSecretKey, + encSecretKeySize, mkEncSecretKey, encSecretKeyByteArray, encSecretKeyByteString, @@ -206,13 +207,17 @@ withSecretKeyPtr (SecretKey secretKey) action = {-# 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 SECRET_KEY_SIZE} +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) @@ -648,8 +653,8 @@ encryptedSign eKey pass msg = wallet_sign keyMaterialPtr msgPtr - (fromIntegral $ B.length msg) - (coerce outSig) + (fromIntegral @Int @CSize $ B.length msg) + (SignaturePtr outSig) pure (if status /= 0 then Left XPrvInternalError else Right (Signature sig)) encryptedDerivePrivate :: @@ -823,7 +828,8 @@ decodeAadFields = do payloadKind <- decodeWord when (payloadKind /= 1) (failDecoder XPrvDecodeError) payloadLen <- decodeWord - when (payloadLen /= fromIntegral secretKeySize) (failDecoder XPrvInvalidCiphertextLength) + when (payloadLen /= fromIntegral @Int @Word encSecretKeySize) $ + failDecoder XPrvInvalidCiphertextLength pubKeyBytes <- decodeBytes chainCodeBytes <- decodeBytes case mkPublicKey pubKeyBytes of @@ -1048,19 +1054,15 @@ withWrappingKey :: passphrase -> Salt -> (WrappingKey -> IO (Either XPrvError a)) -> IO (Either XPrvError a) withWrappingKey pass salt action = do params <- readRuntimeKdfParams - let memBytes = fromIntegral (kdfMemoryKiB params) * 1024 :: Word64 + 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 $ \ppass -> + withByteArray pass $ \passPtr -> withSaltPtr salt $ \saltPtr -> do status <- - wallet_argon2id - outWrappingKeyPtr - (coerce ppass) - (fromIntegral @Int @CULLong $ B.length pass) - saltPtr - (fromIntegral @Word @CULLong $ kdfTimeCost params) - (fromIntegral @Word64 @CSize memBytes) + wallet_argon2id outWrappingKeyPtr (PassPhrasePtr passPtr) passLen saltPtr timeCost memBytes if status == 0 then action wrappingKey @@ -1086,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) ] -- --------------------------------------------------------------------------- @@ -1137,7 +1139,7 @@ foreign import ccall "cardano_crypto_wallet_sign" wallet_sign :: KeyMaterialPtr -> Ptr Word8 -> - Word32 -> + CSize -> SignaturePtr -> IO CInt