diff --git a/.travis.yml b/.travis.yml index 4b3546e..85258bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,14 @@ before_install: - mkdir -p ~/.local/bin - export PATH=$HOME/.local/bin:$PATH - travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' + - sudo apt install build-essential autoconf wget unzip + - wget https://github.com/bitcoin-core/secp256k1/archive/master.zip + - unzip master.zip + - cd secp256k1-master + - ./autogen.sh + - ./configure --enable-experimental --enable-module-ecdh --prefix=/usr + - make -j4 + - sudo make install install: - stack $ARGS setup diff --git a/package.yaml b/package.yaml index 9200525..5b38613 100644 --- a/package.yaml +++ b/package.yaml @@ -43,6 +43,7 @@ library: - mtl - safe-exceptions - transformers + - secp256k1-haskell:+ecdh >= 0.1.5 exposed-modules: - Crypto.Noise @@ -52,6 +53,7 @@ library: - Crypto.Noise.DH - Crypto.Noise.DH.Curve25519 - Crypto.Noise.DH.Curve448 + - Crypto.Noise.DH.Secp256k1 - Crypto.Noise.Exception - Crypto.Noise.HandshakePatterns - Crypto.Noise.Hash @@ -105,6 +107,27 @@ tests: default-extensions: - OverloadedStrings + secp256k1: + main: Main.hs + source-dirs: tests/secp256k1 + dependencies: + - base16-bytestring + - cacophony + - memory + - bytestring + + ghc-options: + - -O2 + - -rtsopts + - -threaded + - -with-rtsopts=-N + - -Wno-name-shadowing + - -Wno-unused-matches + + default-extensions: + - OverloadedStrings + - TypeApplications + benchmarks: bench: main: Main.hs diff --git a/src/Crypto/Noise.hs b/src/Crypto/Noise.hs index 9bce28e..1dbad0f 100644 --- a/src/Crypto/Noise.hs +++ b/src/Crypto/Noise.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE Rank2Types #-} ------------------------------------------------- -- | -- Module : Crypto.Noise @@ -30,6 +31,7 @@ module Crypto.Noise , setLocalStatic , setRemoteEphemeral , setRemoteStatic + , setLightningRotation -- * Classes , Cipher , DH @@ -42,8 +44,9 @@ module Crypto.Noise import Control.Arrow (arr, second, (***)) import Control.Exception.Safe import Control.Lens +import Control.Monad (guard) import Data.ByteArray (ScrubbedBytes, convert) -import Data.Maybe (isJust) +import Data.Maybe (isJust, fromMaybe) import Crypto.Noise.Cipher import Crypto.Noise.DH @@ -86,15 +89,46 @@ writeMessage :: (Cipher c, DH d, Hash h) => ScrubbedBytes -> NoiseState c d h -> NoiseResult c d h -writeMessage msg ns = maybe +writeMessage msg ns2 = maybe (convertHandshakeResult $ resumeHandshake msg ns) (convertTransportResult . encryptMsg) (ns ^. nsSendingCipherState) where + ns = fromMaybe ns2 $ maybeNewNS Send ns2 ctToMsg = arr cipherTextToBytes - updateState = arr $ \cs -> ns & nsSendingCipherState ?~ cs + updateState = arr $ \cs -> ns & nsSendingCipherState ?~ cs + encryptMsg cs = (ctToMsg *** updateState) <$> encryptWithAd mempty msg cs +data SendOrReceive = Send | Receive + +maybeNewNS :: (Cipher a, Hash c) => SendOrReceive -> NoiseState a b c -> Maybe (NoiseState a b c) +maybeNewNS sendOrReceive ns = do + let + selectCK Send = sendingCK + selectCK Receive = receivingCK + selectCS Send = nsSendingCipherState + selectCS Receive = nsReceivingCipherState + ckLens :: Lens' (NoiseState c d a) (ChainingKey a) + ckLens = nsHandshakeState . hsSymmetricState . selectCK sendOrReceive + csLens :: Lens' (NoiseState c d h) (Maybe (CipherState c)) + csLens = selectCS sendOrReceive + oldSK <- ns ^? csLens . _Just . csk . _Just + currentNonce <- ns ^? csLens . _Just . csn + rekeyNonceInteger <- ns ^. nsHandshakeState . hsOpts . lnRekeyNonce + let + rekeyNonce = iterate cipherIncNonce cipherZeroNonce !! fromIntegral rekeyNonceInteger + oldSKBytes = cipherSymToBytes oldSK + [ck, sk] = hashHKDF (ns ^. ckLens) oldSKBytes 2 + updateMaybeCS maybeCS = do + cs <- maybeCS + pure $ (cs & csk .~ (Just $ cipherBytesToSym sk)) + & csn .~ cipherZeroNonce + new = (ns & csLens %~ updateMaybeCS) + & ckLens .~ hashBytesToCK ck + guard $ cipherNonceEq currentNonce rekeyNonce + pure new + -- | Reads a handshake or transport message and returns the embedded payload. If -- the handshake fails, a 'HandshakeError' will be returned. After the -- handshake is complete, if decryption fails a 'DecryptionError' is returned. @@ -109,11 +143,12 @@ readMessage :: (Cipher c, DH d, Hash h) => ScrubbedBytes -> NoiseState c d h -> NoiseResult c d h -readMessage ct ns = maybe +readMessage ct ns2 = maybe (convertHandshakeResult $ resumeHandshake ct ns) (convertTransportResult . decryptMsg) (ns ^. nsReceivingCipherState) where + ns = fromMaybe ns2 $ maybeNewNS Receive ns2 ct' = cipherBytesToText ct updateState = arr $ \cs -> ns & nsReceivingCipherState ?~ cs decryptMsg cs = second updateState <$> decryptWithAd mempty ct' cs diff --git a/src/Crypto/Noise/DH/Secp256k1.hs b/src/Crypto/Noise/DH/Secp256k1.hs new file mode 100644 index 0000000..124785d --- /dev/null +++ b/src/Crypto/Noise/DH/Secp256k1.hs @@ -0,0 +1,65 @@ +{-# LANGUAGE TypeFamilies #-} +------------------------------------------------- +-- | +-- Module : Crypto.Noise.DH.Secp256k1 +-- Maintainer : Janus Troelsen +-- Stability : experimental +-- Portability : POSIX +module Crypto.Noise.DH.Secp256k1 + ( -- * Types + Secp256k1 + ) where + +import Data.ByteArray (ScrubbedBytes, convert) + +import Crypto.Random.Entropy (getEntropy) +--import Crypto.PubKey.ECC.P256K1 (Point, Scalar, pointToBinary, pointDh, scalarFromInteger, pointFromBinary, scalarToBinary, scalarToPoint) +import Crypto.Noise.DH +import Crypto.Secp256k1 + +-- | Represents secp256k1. +data Secp256k1 + +instance DH Secp256k1 where + newtype PublicKey Secp256k1 = PKS256k1 PubKey + newtype SecretKey Secp256k1 = SKS256k1 SecKey + + dhName _ = "secp256k1" + dhLength _ = 33 + dhGenKey = genKey + dhPerform = dh + dhPubToBytes = pubToBytes + dhBytesToPub = bytesToPub + dhSecToBytes = secToBytes + dhBytesToPair = bytesToPair + dhPubEq = pubEq + +genKey :: IO (KeyPair Secp256k1) +genKey = do + r <- getEntropy 32 :: IO ScrubbedBytes + case bytesToPair r of + Just x -> return x + Nothing -> genKey + +dh :: SecretKey Secp256k1 -> PublicKey Secp256k1 -> ScrubbedBytes +dh (SKS256k1 sk) (PKS256k1 pk) = convert $ ecdh pk sk + +pubToBytes :: PublicKey Secp256k1 -> ScrubbedBytes +pubToBytes (PKS256k1 pk) = convert $ exportPubKey True pk + +bytesToPub :: ScrubbedBytes -> Maybe (PublicKey Secp256k1) +bytesToPub bytes = fmap PKS256k1 $ importPubKey $ convert bytes + +secToBytes :: SecretKey Secp256k1 -> ScrubbedBytes +secToBytes (SKS256k1 sk) = convert $ getSecKey sk + +bytesToPair :: ScrubbedBytes -> Maybe (KeyPair Secp256k1) +bytesToPair bs = do + sk <- secKey $ convert bs + let pk = derivePubKey sk + return (SKS256k1 sk, PKS256k1 pk) + +pubEq :: PublicKey Secp256k1 + -> PublicKey Secp256k1 + -> Bool +pubEq (PKS256k1 a) (PKS256k1 b) = a == b diff --git a/src/Crypto/Noise/Internal/Handshake/State.hs b/src/Crypto/Noise/Internal/Handshake/State.hs index 96e9ab7..09e214d 100644 --- a/src/Crypto/Noise/Internal/Handshake/State.hs +++ b/src/Crypto/Noise/Internal/Handshake/State.hs @@ -39,6 +39,7 @@ data HandshakeOpts d = , _hoLocalStatic :: Maybe (KeyPair d) , _hoRemoteEphemeral :: Maybe (PublicKey d) , _hoRemoteStatic :: Maybe (PublicKey d) + , _lnRekeyNonce :: Maybe Integer } $(makeLenses ''HandshakeOpts) @@ -80,6 +81,7 @@ defaultHandshakeOpts r p = , _hoLocalStatic = Nothing , _hoRemoteEphemeral = Nothing , _hoRemoteStatic = Nothing + , _lnRekeyNonce = Nothing } -- | Sets the local ephemeral key. @@ -106,6 +108,12 @@ setRemoteStatic :: Maybe (PublicKey d) -> HandshakeOpts d setRemoteStatic k opts = opts { _hoRemoteStatic = k } +-- | Sets the nonce number at which Lightning (BOLT-08) rekeying will occur. +setLightningRotation :: Maybe Integer + -> HandshakeOpts d + -> HandshakeOpts d +setLightningRotation n opts = opts { _lnRekeyNonce = n } + -- | Given a protocol name, returns the full handshake name according to the -- rules in section 8. mkHandshakeName :: forall c d h proxy. (Cipher c, DH d, Hash h) diff --git a/src/Crypto/Noise/Internal/SymmetricState.hs b/src/Crypto/Noise/Internal/SymmetricState.hs index 28724fc..9b2d0f3 100644 --- a/src/Crypto/Noise/Internal/SymmetricState.hs +++ b/src/Crypto/Noise/Internal/SymmetricState.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE TemplateHaskell, ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell, ScopedTypeVariables, TypeApplications #-} ----------------------------------------------------- -- | -- Module : Crypto.Noise.Internal.SymmetricState @@ -19,8 +19,9 @@ import Crypto.Noise.Internal.CipherState data SymmetricState c h = SymmetricState { _ssCipher :: CipherState c - , _ssck :: ChainingKey h , _ssh :: Either ScrubbedBytes (Digest h) + , _sendingCK :: ChainingKey h + , _receivingCK :: ChainingKey h } $(makeLenses ''SymmetricState) @@ -29,7 +30,7 @@ $(makeLenses ''SymmetricState) symmetricState :: forall c h. (Cipher c, Hash h) => ScrubbedBytes -> SymmetricState c h -symmetricState protoName = SymmetricState cs ck h +symmetricState protoName = SymmetricState cs h ck ck where hashLen = hashLength (Proxy :: Proxy h) shouldHash = length protoName > hashLen @@ -44,10 +45,12 @@ mixKey :: (Cipher c, Hash h) => ScrubbedBytes -> SymmetricState c h -> SymmetricState c h -mixKey keyMat ss = ss & ssCipher .~ cs - & ssck .~ hashBytesToCK ck +mixKey keyMat ss = + ss & ssCipher .~ cs + & sendingCK .~ hashBytesToCK ck + & receivingCK .~ hashBytesToCK ck where - [ck, k] = hashHKDF (ss ^. ssck) keyMat 2 + [ck, k] = hashHKDF (ss ^. sendingCK) keyMat 2 -- k is truncated automatically by cipherBytesToSym cs = cipherState . Just . cipherBytesToSym $ k @@ -64,10 +67,12 @@ mixKeyAndHash :: (Cipher c, Hash h) => ScrubbedBytes -> SymmetricState c h -> SymmetricState c h -mixKeyAndHash keyMat ss = ss' & ssCipher .~ cs - & ssck .~ hashBytesToCK ck +mixKeyAndHash keyMat ss = + ss' & ssCipher .~ cs + & sendingCK .~ hashBytesToCK ck + & receivingCK .~ hashBytesToCK ck where - [ck, h, k] = hashHKDF (ss ^. ssck) keyMat 3 + [ck, h, k] = hashHKDF (ss ^. sendingCK) keyMat 3 ss' = mixHash h ss cs = cipherState . Just . cipherBytesToSym $ k @@ -102,9 +107,10 @@ decryptAndHash ct ss = do split :: (Cipher c, Hash h) => SymmetricState c h -> (CipherState c, CipherState c) -split ss = (c1, c2) +split ss = + (c1, c2) where - [k1, k2] = hashHKDF (ss ^. ssck) mempty 2 + [k1, k2] = hashHKDF (ss ^. sendingCK) mempty 2 c1 = cipherState . Just . cipherBytesToSym $ k1 c2 = cipherState . Just . cipherBytesToSym $ k2 diff --git a/tests/secp256k1/Main.hs b/tests/secp256k1/Main.hs new file mode 100644 index 0000000..5f21819 --- /dev/null +++ b/tests/secp256k1/Main.hs @@ -0,0 +1,171 @@ +module Main where + +import Prelude hiding (replicate, length, splitAt) +import Data.Bits (shiftR) +import Data.ByteString.Base16 (encode, decode) +import Data.ByteArray (convert, length) +import Data.ByteString (ByteString, pack, splitAt) +import Data.Maybe (fromJust, isNothing) +import Data.Monoid ((<>)) +import Control.Monad (unless, foldM) +import System.Exit + +import Crypto.Noise (NoiseState, NoiseResult(..), ScrubbedBytes, HandshakeOpts, writeMessage, readMessage, noiseState, setLocalEphemeral, setLocalStatic, setRemoteStatic, defaultHandshakeOpts, HandshakeRole(..), setLightningRotation) +import Crypto.Noise.Cipher.ChaChaPoly1305 (ChaChaPoly1305) +import Crypto.Noise.DH (KeyPair, dhBytesToPair, SecretKey, PublicKey) +import Crypto.Noise.DH.Secp256k1 (Secp256k1) +import Crypto.Noise.Hash.SHA256 (SHA256) +import Crypto.Noise.HandshakePatterns (noiseXK) + +hexToPair :: ByteString -> KeyPair Secp256k1 +hexToPair x = fromJust $ dhBytesToPair $ convert $ fst $ decode x + +pairLocal :: KeyPair Secp256k1 +pairLocal = hexToPair "1111111111111111111111111111111111111111111111111111111111111111" + +secRemote :: SecretKey Secp256k1 +pubRemote :: PublicKey Secp256k1 +(secRemote, pubRemote) = hexToPair "2121212121212121212121212121212121212121212121212121212121212121" + +test_handshake :: IO Bool +test_handshake = do + -- see + -- https://github.com/cdecker/lightning/blob/pywire/contrib/pyln-proto/tests/test_wire.py#L29 + -- which uses values from + -- https://github.com/lightningnetwork/lightning-rfc/blob/master/08-transport.md#initiator-tests + -- the test has been amended with the patch in test_wire.patch + let ilocalEphemeralKey = hexToPair "1212121212121212121212121212121212121212121212121212121212121212" + + -- Initiator + let idho = defaultHandshakeOpts InitiatorRole "lightning" :: HandshakeOpts Secp256k1 + iiho = setLocalStatic (Just pairLocal) + . setLocalEphemeral (Just ilocalEphemeralKey) + . setRemoteStatic (Just pubRemote) -- communicated out-of-band + . setLightningRotation (Just 1000) + $ idho + + -- Responder + let rlocalEphemeralKey = hexToPair "2222222222222222222222222222222222222222222222222222222222222222" + + let rdho = defaultHandshakeOpts ResponderRole "lightning" :: HandshakeOpts Secp256k1 + rrho = setLocalStatic (Just (secRemote, pubRemote)) + . setLocalEphemeral (Just rlocalEphemeralKey) + . setLightningRotation (Just 1000) + $ rdho + + -- Initiator + let ins = noiseState iiho noiseXK :: NoiseState ChaChaPoly1305 Secp256k1 SHA256 + + -- Responder + let rns = noiseState rrho noiseXK :: NoiseState ChaChaPoly1305 Secp256k1 SHA256 + + let writeResult = writeMessage "" ins + let NoiseResultMessage ciphertext ins = writeResult + --putStrLn $ "Main.hs ciphertext: " ++ (show $ encode $ convert $ ciphertext) + let readResult = readMessage ciphertext rns + -- note how version byte is missing + unless (ciphertext == convert (fst $ decode "036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a")) (error "act1") + let NoiseResultMessage _ rns = readResult + --putStrLn $ "act one received: " ++ (show $ (convert $ plaintext :: ByteString)) + let writeActTwoResult = writeMessage "" rns + let NoiseResultMessage ciphertext rns = writeActTwoResult + -- note how version byte is missing + unless (ciphertext == convert (fst $ decode "02466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae")) $ error "act2" + + let readActTwoResult = readMessage ciphertext ins + let NoiseResultMessage _ ins = readActTwoResult + + -- note how we are not sending the public key like in the python code linked + let writeActThreeResult = writeMessage "" ins + let NoiseResultMessage ciphertext ins = writeActThreeResult + -- note how version byte is missing + unless (ciphertext == convert (fst $ decode "b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba")) $ error $ "act3: " ++ show (encode $ convert ciphertext) + + let readActThreeResult = readMessage ciphertext rns + let NoiseResultMessage plaintext rns = readActThreeResult + putStrLn $ "act three received: " ++ show (encode $ convert plaintext) + + let msgtowrite = "\x68\x65\x6c\x6c\x6f" + + (lastm, ins) <- sendLnMsg msgtowrite ins + unless (lastm == convert (fst $ decode "cf2b30ddf0cf3f80e7c35a6e6730b59fe802473180f396d88a8fb0db8cbcf25d2f214cf9ea1d95")) $ error $ "wrong msg1: " ++ show (encode $ convert lastm) + + (lastm, ins) <- foldM (\(_lastm, tins) _ -> do + (m2, newins) <- sendLnMsg msgtowrite tins + return (m2, newins) + ) ("", ins) ([1..498] :: [Integer]) + + unless (lastm == convert (fst $ decode "c95576afee4591869808a1c28e1fc7e5a578d86e569e1680e017b4f7a4df74ba222cf08e4ab8b1")) $ error $ "wrong msg2: " ++ show (encode $ convert lastm) + + (lastm, ins) <- sendLnMsg msgtowrite ins + unless (lastm == convert (fst $ decode "0b0b7c16d2930e64a2db554f211f3bb279bf29701642655ce87e168ac0c6a19cdfe2b631d9e580")) $ error $ "wrong msg3: " ++ show (encode $ convert lastm) + + (lastm, ins) <- sendLnMsg msgtowrite ins + unless (lastm == convert (fst $ decode "178cb9d7387190fa34db9c2d50027d21793c9bc2d40b1e14dcf30ebeeeb220f48364f7a4c68bf8")) $ error $ "wrong msg4: " ++ show (encode $ convert lastm) + + (lastm, ins) <- foldM (\(_lastm, tins) _ -> do + (m2, newins) <- sendLnMsg msgtowrite tins + return (m2, newins) + ) ("", ins) ([1..499] :: [Integer]) + + (lastm, ins) <- sendLnMsg msgtowrite ins + unless (lastm == convert (fst $ decode "4a2f3cc3b5e78ddb83dcb426d9863d9d9a723b0337c89dd0b005d89f8d3c05c52b76b29b740f09")) $ error $ "wrong msg5: " ++ show (encode $ convert lastm) + + -- OTHER DIRECTION! check that chaining_key is not shared between send/receive + + (lastm, rns) <- sendLnMsg msgtowrite rns + unless (lastm == convert (fst $ decode "5bed0e4d7e2bc28afff2c05dd8fd7a24da81dc17be87e87504e5266a5301529467b98884e0b269")) $ error $ "wrong msg6: " ++ show (encode $ convert lastm) + + let (p1, p2) = splitAt 18 $ convert lastm + NoiseResultMessage plain_len ins <- pure $ readMessage (convert p1) ins + NoiseResultMessage plain_msg ins <- pure $ readMessage (convert p2) ins + + (lastm, rns, ins) <- foldM (\(_lastm, trns, ins) _ -> do + (m2, newrns) <- sendLnMsg msgtowrite trns + let (p1, p2) = splitAt 18 $ convert m2 + NoiseResultMessage plain_len ins <- pure $ readMessage (convert p1) ins + NoiseResultMessage plain_msg ins <- pure $ readMessage (convert p2) ins + return (m2, newrns, ins) + ) ("", rns, ins) ([1..499] :: [Integer]) + + print $ encode $ convert lastm + + (lastm, rns) <- sendLnMsg msgtowrite rns + unless (lastm == convert (fst $ decode "bfd031ec37bfd43f29401e2c5a465256ec7efe5258e70d7b0271200afd24239f7d3adc01e0be1f")) $ error $ "wrong msg7: " ++ show (encode $ convert lastm) + + let (p1, p2) = splitAt 18 $ convert lastm + + NoiseResultMessage plain_len ins <- pure $ readMessage (convert p1) ins + NoiseResultMessage plain_msg ins <- pure $ readMessage (convert p2) ins + return True + +i2osp :: Int -> ByteString +i2osp a = + pack [firstByte, secondByte] + where + firstByte = fromIntegral $ a `shiftR` 8 + secondByte = fromIntegral a + +sendLnMsg :: ScrubbedBytes -> NoiseState ChaChaPoly1305 Secp256k1 SHA256 -> IO (ScrubbedBytes, NoiseState ChaChaPoly1305 Secp256k1 SHA256) +sendLnMsg msg ins = do + let lenBytes = convert $ i2osp $ length msg + NoiseResultMessage lengthPart ins <- pure $ writeMessage lenBytes ins + NoiseResultMessage msgPart ins <- pure $ writeMessage msg ins + let lpbs = convert lengthPart + mpbs = convert msgPart + lengthPrefixed = lpbs <> mpbs :: ByteString + pure (convert lengthPrefixed, ins) + +test_bytesToPair :: Bool +test_bytesToPair = + isNothing $ dhBytesToPair @Secp256k1 $ convert $ pack [0] + +main :: IO () +main = do + res <- test_handshake + let res2 = test_bytesToPair + if res && res2 then + exitSuccess + else do + putStrLn "a secp256k1 test failed" + exitFailure diff --git a/tests/secp256k1/test_wire.patch b/tests/secp256k1/test_wire.patch new file mode 100644 index 0000000..31e2c2a --- /dev/null +++ b/tests/secp256k1/test_wire.patch @@ -0,0 +1,64 @@ +diff --git a/contrib/pyln-proto/tests/test_wire.py b/contrib/pyln-proto/tests/test_wire.py +index 093792ff..b08069ca 100644 +--- a/contrib/pyln-proto/tests/test_wire.py ++++ b/contrib/pyln-proto/tests/test_wire.py +@@ -99,6 +99,59 @@ def test_handshake(): + assert(lc1.chaining_key == lc2.chaining_key) + assert(hexlify(lc1.chaining_key) == b'919219dbb2920afa8db80f9a51787a840bcf111ed8d588caf9ab4be716e42b01') + ++ msg = unhexlify(b'68656c6c6f') ++ ++ # Send 498 more messages, to get just below the switch threshold ++ for i in range(0, 499): ++ lc1.send_message(msg) ++ c2.recv(18 + 21) ++ ++ old_sck = lc1.sck ++ lc1.send_message(msg) # rotates here ++ assert lc1.sck != old_sck ++ m = c2.recv(18 + 21) ++ # this message would be the same without rotation ++ # since rotation takes effect only in the next message ++ assert m[:4] == unhexlify(b'0b0b7c16') ++ ++ assert lc1.sck[:4] == unhexlify(b'cc2c6e46') ++ assert lc1.sk[:4] == unhexlify(b'3fbdc101') ++ ++ lc1.send_message(msg) ++ m = c2.recv(18 + 21) ++ assert m == unhexlify(b'178cb9d7387190fa34db9c2d50027d21793c9bc2d40b1e14dcf30ebeeeb220f48364f7a4c68bf8') ++ ++ for i in range(0, 499): ++ lc1.send_message(msg) ++ c2.recv(18 + 21) ++ ++ lc1.send_message(msg) ++ m = c2.recv(18 + 21) ++ assert m == unhexlify(b'4a2f3cc3b5e78ddb83dcb426d9863d9d9a723b0337c89dd0b005d89f8d3c05c52b76b29b740f09'), m.hex() ++ ++ # OTHER DIRECTION!!!! check that chaining_key is not shared ++ ++ lc2.send_message(msg) ++ lc1.rn += 2 ++ m = c1.recv(18 + 21) ++ assert m == unhexlify(b'5bed0e4d7e2bc28afff2c05dd8fd7a24da81dc17be87e87504e5266a5301529467b98884e0b269'), m.hex() ++ ++ old = lc1.rck ++ ++ for i in range(0, 500): ++ lc2.send_message(msg) ++ lc1.rn += 2 ++ lc1._maybe_rotate_keys() ++ m = c1.recv(18 + 21) ++ ++ assert lc1.rck != old ++ ++ # this message is the first to have key rotation effective: ++ assert m == unhexlify(b'bfd031ec37bfd43f29401e2c5a465256ec7efe5258e70d7b0271200afd24239f7d3adc01e0be1f'), m.hex() ++ lc2.connection.send(m) ++ lc1.rn -= 2 ++ lc1.read_message() ++ + + def test_shake(): + rs_privkey = PrivateKey(unhexlify('2121212121212121212121212121212121212121212121212121212121212121')) diff --git a/tests/vectors/Generate.hs b/tests/vectors/Generate.hs index 407e2e7..28aa52c 100644 --- a/tests/vectors/Generate.hs +++ b/tests/vectors/Generate.hs @@ -151,6 +151,7 @@ allHandshakes = do dh <- [ WrapDHType Curve25519 , WrapDHType Curve448 + , WrapDHType Secp256k1 ] hash <- [ WrapHashType BLAKE2b diff --git a/tests/vectors/Keys.hs b/tests/vectors/Keys.hs index 1f0ab3c..67ae492 100644 --- a/tests/vectors/Keys.hs +++ b/tests/vectors/Keys.hs @@ -8,6 +8,7 @@ import Crypto.Noise (ScrubbedBytes, convert) import Crypto.Noise.DH import Crypto.Noise.DH.Curve25519 import Crypto.Noise.DH.Curve448 +import Crypto.Noise.DH.Secp256k1 import Types import VectorFile @@ -232,6 +233,7 @@ privateToPublic :: SomeDHType -> Maybe ScrubbedBytes privateToPublic (WrapDHType Curve25519) k = fmap (dhPubToBytes . snd) (dhBytesToPair k :: Maybe (KeyPair Curve25519)) privateToPublic (WrapDHType Curve448) k = fmap (dhPubToBytes . snd) (dhBytesToPair k :: Maybe (KeyPair Curve448)) +privateToPublic (WrapDHType Secp256k1) k = fmap (dhPubToBytes . snd) (dhBytesToPair k :: Maybe (KeyPair Secp256k1)) psk :: ScrubbedBytes psk = "This is my Austrian perspective!" @@ -240,18 +242,22 @@ initiatorEphemeral :: SomeDHType -> ScrubbedBytes initiatorEphemeral (WrapDHType Curve25519) = hexToSB "893e28b9dc6ca8d611ab664754b8ceb7bac5117349a4439a6b0569da977c464a" initiatorEphemeral (WrapDHType Curve448) = hexToSB "7fd26c8b8a0d5c98c85ff9ca1d7bc66d78578b9f2c4c170850748b27992767e6ea6cc9992a561c9d19dfc342e260c280ef4f3f9b8f879d4e" +initiatorEphemeral (WrapDHType Secp256k1) = hexToSB "1212121212121212121212121212121212121212121212121212121212121212" responderEphemeral :: SomeDHType -> ScrubbedBytes responderEphemeral (WrapDHType Curve25519) = hexToSB "bbdb4cdbd309f1a1f2e1456967fe288cadd6f712d65dc7b7793d5e63da6b375b" responderEphemeral (WrapDHType Curve448) = hexToSB "3facf7503ebee252465689f1d4e3b1dd219639ef9de4ffd6049d6d71a0f62126840febb99042421ce12af6626d98d9170260390fbc8399a5" +responderEphemeral (WrapDHType Secp256k1) = hexToSB "1212121212121212121212121212121212121212121212121212121212121212" initiatorStatic :: SomeDHType -> ScrubbedBytes initiatorStatic (WrapDHType Curve25519) = hexToSB "e61ef9919cde45dd5f82166404bd08e38bceb5dfdfded0a34c8df7ed542214d1" initiatorStatic (WrapDHType Curve448) = hexToSB "34d564c4be963d1b2a89fcfe83e6a72b5e3f5e3127f9f596ffc7575e418dfc1f4e827cfc10c9fed38e92ad56ddf8f08571430df2e76d5411" +initiatorStatic (WrapDHType Secp256k1) = hexToSB "1111111111111111111111111111111111111111111111111111111111111111" responderStatic :: SomeDHType -> ScrubbedBytes responderStatic (WrapDHType Curve25519) = hexToSB "4a3acbfdb163dec651dfa3194dece676d437029c62a408b4c5ea9114246e4893" responderStatic (WrapDHType Curve448) = hexToSB "a9b45971180882a79b89a3399544a425ef8136d278efa443ed67d3ff9d36e883bc330c6295bbf6ed73ff6fd10cbed767ad05ce03ebd27c7c" +responderStatic (WrapDHType Secp256k1) = hexToSB "2121212121212121212121212121212121212121212121212121212121212121" diff --git a/tests/vectors/Types.hs b/tests/vectors/Types.hs index ebd6856..cfead96 100644 --- a/tests/vectors/Types.hs +++ b/tests/vectors/Types.hs @@ -16,6 +16,7 @@ import Crypto.Noise.Cipher.ChaChaPoly1305 import Crypto.Noise.Cipher.AESGCM import Crypto.Noise.DH.Curve25519 import Crypto.Noise.DH.Curve448 +import Crypto.Noise.DH.Secp256k1 import Crypto.Noise.HandshakePatterns import Crypto.Noise.Hash.SHA256 import Crypto.Noise.Hash.SHA512 @@ -101,6 +102,7 @@ data SomeCipherType where data DHType :: * -> * where Curve25519 :: DHType Curve25519 Curve448 :: DHType Curve448 + Secp256k1 :: DHType Secp256k1 data SomeDHType where WrapDHType :: forall d. DH d => DHType d -> SomeDHType @@ -181,6 +183,7 @@ dhMap :: [(ByteString, SomeDHType)] dhMap = [ ("25519", WrapDHType Curve25519) , ("448" , WrapDHType Curve448) + , ("secp256k1", WrapDHType Secp256k1) ] cipherMap :: [(ByteString, SomeCipherType)] @@ -313,6 +316,7 @@ instance Show SomeCipherType where instance Show SomeDHType where show (WrapDHType Curve25519) = "25519" show (WrapDHType Curve448) = "448" + show (WrapDHType Secp256k1) = "secp256k1" instance Show SomeHashType where show (WrapHashType BLAKE2b) = "BLAKE2b" diff --git a/vectors/cacophony.txt b/vectors/cacophony.txt index b8a271e..3a39a13 100644 --- a/vectors/cacophony.txt +++ b/vectors/cacophony.txt @@ -34847,6 +34847,23 @@ "ciphertext": "d42ad8f27de8c40b37f082a63904697d2b76de26815b380c9b2ec41a18f30a251032946ab9" } ] +}, +{ +"protocol_name": "Noise_XK_secp256k1_ChaChaPoly_SHA256", +"init_prologue": "6c696768746e696e67", +"init_static": "1111111111111111111111111111111111111111111111111111111111111111", +"init_ephemeral": "1212121212121212121212121212121212121212121212121212121212121212", +"init_remote_static": "028d7500dd4c12685d1f568b4c2b5048e8534b873319f3a8daa612b469132ec7f7", +"resp_prologue": "6c696768746e696e67", +"resp_static": "2121212121212121212121212121212121212121212121212121212121212121", +"resp_ephemeral": "1212121212121212121212121212121212121212121212121212121212121212", +"handshake_hash": "9d1ffbb639e7e20021d9259491dc7b160aab270fb1339ef135053f6f2cebe9ce", +"messages": [ +{ +"payload": "", +"ciphertext": "036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a" } ] -} \ No newline at end of file +} +] +}