-
Notifications
You must be signed in to change notification settings - Fork 214
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Not tested yet. For the time being we hard-code the testnet protocol magic in the signing.
- Loading branch information
Showing
4 changed files
with
203 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
{-# LANGUAGE DataKinds #-} | ||
{-# LANGUAGE LambdaCase #-} | ||
{-# LANGUAGE RankNTypes #-} | ||
{-# LANGUAGE TypeApplications #-} | ||
|
||
-- | | ||
-- Copyright: © 2018-2019 IOHK | ||
-- License: MIT | ||
-- | ||
-- This module provides functionality for signing transactions. | ||
-- | ||
-- It relies on the binary CBOR format of transactions, looking up address keys | ||
-- from AddressPools, and cardano-crypto for the actual signing. | ||
module Cardano.Wallet.Primitive.Signing | ||
( -- * Sign transactions | ||
mkStdTx | ||
|
||
-- * Extra | ||
, shuffle | ||
) | ||
where | ||
|
||
|
||
import Prelude | ||
|
||
import Cardano.Wallet.Binary | ||
( TxWitness (..), encodeTx, toByteString ) | ||
import Cardano.Wallet.Primitive.AddressDerivation | ||
( ChangeChain (ExternalChain) | ||
, Depth (AddressK, RootK) | ||
, Key | ||
, Passphrase (..) | ||
, XPrv | ||
, XPub | ||
, deriveAccountPrivateKey | ||
, deriveAddressPrivateKey | ||
, publicKey | ||
, unKey | ||
) | ||
import Cardano.Wallet.Primitive.AddressDiscovery | ||
( SeqState (externalPool), lookupAddress ) | ||
import Cardano.Wallet.Primitive.Types | ||
( Address, Hash (..), Tx (..), TxIn, TxOut (..) ) | ||
import Control.Monad | ||
( forM, forM_ ) | ||
import Crypto.Hash | ||
( Blake2b_256, hash ) | ||
import Crypto.Number.Generate | ||
( generateBetween ) | ||
import Data.ByteArray | ||
( convert ) | ||
import Data.ByteString | ||
( ByteString ) | ||
import Data.Vector.Mutable | ||
( IOVector ) | ||
|
||
import qualified Cardano.Crypto.Wallet as CC | ||
import qualified Codec.CBOR.Encoding as CBOR | ||
import qualified Data.Vector as V | ||
import qualified Data.Vector.Mutable as MV | ||
|
||
-- | Build a transaction | ||
|
||
-- | Construct a standard transaction | ||
-- | ||
-- " Standard " here refers to the fact that we do not deal with redemption, | ||
-- multisignature transactions, etc. | ||
-- | ||
mkStdTx | ||
:: Monad m | ||
=> SeqState | ||
-> (Key 'RootK XPrv, Passphrase "encryption") | ||
-> (forall a. [a] -> m [a]) | ||
-- ^ Function for shuffeling outputs | ||
-> [(TxIn, TxOut)] | ||
-- ^ Selected inputs | ||
-> [TxOut] | ||
-- ^ Selected outputs (including change) | ||
-> m (Maybe (Tx, [TxWitness])) | ||
mkStdTx seqState (rootPrv, pwd) shuffle' ownedIns unshuffledOuts = do | ||
|
||
outs <- shuffle' unshuffledOuts | ||
|
||
return $ do | ||
let ins = (fmap fst ownedIns) | ||
tx = Tx ins outs | ||
txSigData = hashTx tx | ||
|
||
txWitnesses <- forM ownedIns (\(_in, TxOut addr _c) -> | ||
mkWitness txSigData <$> keyFrom addr) | ||
|
||
return (tx, txWitnesses) | ||
|
||
where | ||
hashTx :: Tx -> Hash "tx" | ||
hashTx tx = Hash | ||
$ convert | ||
$ (hash @ByteString @Blake2b_256) | ||
$ toByteString | ||
$ encodeTx tx | ||
|
||
keyFrom :: Address -> (Maybe (Key 'AddressK XPrv)) | ||
keyFrom addr = do | ||
-- We are assuming there is only one account | ||
let account = minBound | ||
let accountPrv = deriveAccountPrivateKey pwd rootPrv account | ||
|
||
-- We are ignoring the new state/pool. We won't discover any new | ||
-- addresses when submitting transactions. | ||
index <- fst $ lookupAddress addr (externalPool seqState) | ||
|
||
return $ deriveAddressPrivateKey pwd accountPrv ExternalChain index | ||
|
||
mkWitness :: Hash "tx" -> Key 'AddressK XPrv -> TxWitness | ||
mkWitness tx xPrv = | ||
PublicKeyWitness $ | ||
encodeXPub (publicKey xPrv) <> | ||
getHash (sign (SignTx tx) (xPrv, pwd)) | ||
|
||
|
||
|
||
|
||
-- | Used for signing transactions | ||
sign | ||
:: SignTag | ||
-> (Key 'AddressK XPrv, Passphrase "encryption") | ||
-> Hash "signature" | ||
sign tag (key, (Passphrase pwd)) = | ||
Hash . CC.unXSignature $ CC.sign pwd (unKey key) (signTag tag) | ||
|
||
|
||
-- | To protect agains replay attacks (i.e. when an attacker intercepts a | ||
-- signed piece of data and later sends it again), we add a tag to all data | ||
-- that we sign. This ensures that even if some bytestring can be | ||
-- deserialized into two different types of messages (A and B), the attacker | ||
-- can't take message A and send it as message B. | ||
-- | ||
-- We also automatically add the network tag ('protocolMagic') whenever it | ||
-- makes sense, to ensure that things intended for testnet won't work for | ||
-- mainnet. | ||
-- | ||
-- The wallet only cares about the 'SignTx' tag. In 'cardano-sl' there was | ||
-- a lot more cases. | ||
newtype SignTag | ||
= SignTx (Hash "tx") | ||
deriving (Eq, Ord, Show) | ||
|
||
|
||
-- | Encode magic bytes & the contents of a @SignTag@. Magic bytes are | ||
-- guaranteed to be different (and begin with a different byte) for different | ||
-- tags. | ||
signTag :: SignTag -> ByteString | ||
signTag = \case | ||
SignTx (Hash payload) -> "\x01" <> network <> payload | ||
where | ||
network = toByteString . CBOR.encodeInt32 $ pm | ||
pm = 1097911063 -- testnet; TODO: need to decide on how to pass this in | ||
|
||
|
||
-- | Get the underlying ByteString | ||
encodeXPub :: (Key level XPub) -> ByteString | ||
encodeXPub = CC.unXPub . unKey | ||
|
||
|
||
{------------------------------------------------------------------------------- | ||
Extra | ||
-------------------------------------------------------------------------------} | ||
|
||
-- | Shuffles a list. Meant to be passed to @mkStdTx@. | ||
-- | ||
-- Implementation was copied from cardano-sl | ||
shuffle :: [a] -> IO [a] | ||
shuffle = modifyInPlace $ \v -> do | ||
let (lo, hi) = (0, MV.length v - 1) | ||
forM_ [lo .. hi] $ \i -> do | ||
j <- fromInteger <$> generateBetween (fromIntegral lo) (fromIntegral hi) | ||
swapElems v i j | ||
where | ||
swapElems :: IOVector a -> Int -> Int -> IO () | ||
swapElems v i j = do | ||
x <- MV.read v i | ||
y <- MV.read v j | ||
MV.write v i y | ||
MV.write v j x | ||
|
||
modifyInPlace :: forall a. (IOVector a -> IO ()) -> [a] -> IO [a] | ||
modifyInPlace f xs = do | ||
v' <- V.thaw $ V.fromList xs | ||
f v' | ||
V.toList <$> V.freeze v' | ||
|
||
|