packages feed

bitcoin-payment-channel 0.4.0.1 → 0.5.0.0

raw patch · 14 files changed

+315/−400 lines, 14 filesdep +taggeddep +test-frameworkdep +test-framework-quickcheck2PVP ok

version bump matches the API change (PVP)

Dependencies added: tagged, test-framework, test-framework-quickcheck2

API changes (from Hackage documentation)

- Data.Bitcoin.PaymentChannel.Internal.State: pcsChannelID :: PaymentChannelState -> OutPoint
- Data.Bitcoin.PaymentChannel.Movable: ChannelPair :: BitcoinAmount -> ReceiverPaymentChannel -> ReceiverPaymentChannel -> ChannelPair
- Data.Bitcoin.PaymentChannel.Movable: NewNeedsUpdate :: ChannelPair -> BitcoinAmount -> PartialPayment
- Data.Bitcoin.PaymentChannel.Movable: OldNeedsUpdate :: ChannelPair -> BitcoinAmount -> PartialPayment
- Data.Bitcoin.PaymentChannel.Movable: Settled :: BitcoinAmount -> ReceiverPaymentChannel -> MovableChan
- Data.Bitcoin.PaymentChannel.Movable: Unsettled :: ChannelPair -> Maybe PartialPayment -> MovableChan
- Data.Bitcoin.PaymentChannel.Movable: [beginState] :: MovableChan -> ReceiverPaymentChannel
- Data.Bitcoin.PaymentChannel.Movable: [beginVal'] :: ChannelPair -> BitcoinAmount
- Data.Bitcoin.PaymentChannel.Movable: [beginVal] :: MovableChan -> BitcoinAmount
- Data.Bitcoin.PaymentChannel.Movable: [channels] :: MovableChan -> ChannelPair
- Data.Bitcoin.PaymentChannel.Movable: [finishPay] :: MovableChan -> Maybe PartialPayment
- Data.Bitcoin.PaymentChannel.Movable: [newState] :: ChannelPair -> ReceiverPaymentChannel
- Data.Bitcoin.PaymentChannel.Movable: [oldState] :: ChannelPair -> ReceiverPaymentChannel
- Data.Bitcoin.PaymentChannel.Movable: checkChangeValueMatch :: BitcoinAmount -> FullPayment -> Either PayChanError FullPayment
- Data.Bitcoin.PaymentChannel.Movable: data ChannelPair
- Data.Bitcoin.PaymentChannel.Movable: data MovableChan
- Data.Bitcoin.PaymentChannel.Movable: data PartialPayment
- Data.Bitcoin.PaymentChannel.Movable: getStateByInfo :: MovableChan -> BitcoinLockTime -> OutPoint -> Maybe (ReceiverPaymentChannel, BitcoinAmount)
- Data.Bitcoin.PaymentChannel.Movable: getStateForClosing :: MovableChan -> (ReceiverPaymentChannel, BitcoinAmount)
- Data.Bitcoin.PaymentChannel.Movable: instance Data.Bitcoin.PaymentChannel.Types.PaymentChannel Data.Bitcoin.PaymentChannel.Movable.MovableChan
- Data.Bitcoin.PaymentChannel.Movable: instance Data.Serialize.Serialize Data.Bitcoin.PaymentChannel.Movable.ChannelPair
- Data.Bitcoin.PaymentChannel.Movable: instance Data.Serialize.Serialize Data.Bitcoin.PaymentChannel.Movable.MovableChan
- Data.Bitcoin.PaymentChannel.Movable: instance Data.Serialize.Serialize Data.Bitcoin.PaymentChannel.Movable.PartialPayment
- Data.Bitcoin.PaymentChannel.Movable: markAsSettled :: MovableChan -> Maybe MovableChan
- Data.Bitcoin.PaymentChannel.Movable: moveChannel :: MovableChan -> (Hash256 -> Signature) -> Address -> BitcoinAmount -> Maybe (Tx, MovableChan)
- Data.Bitcoin.PaymentChannel.Movable: newMovableChan :: ChannelParameters -> FundingTxInfo -> FullPayment -> Either PayChanError (BitcoinAmount, MovableChan, BitcoinAmount)
- Data.Bitcoin.PaymentChannel.Movable: newRecvPay :: ChannelPair -> FullPayment -> Either PayChanError (BitcoinAmount, MovableChan, BitcoinAmount)
- Data.Bitcoin.PaymentChannel.Movable: oldRecvPay :: ChannelPair -> FullPayment -> Either PayChanError (BitcoinAmount, MovableChan, BitcoinAmount)
- Data.Bitcoin.PaymentChannel.Movable: receiveSecondPayment :: PartialPayment -> FullPayment -> Either PayChanError (BitcoinAmount, MovableChan, BitcoinAmount)
- Data.Bitcoin.PaymentChannel.Movable: recvSinglePayment :: MovableChan -> FullPayment -> Either PayChanError (BitcoinAmount, MovableChan, BitcoinAmount)
- Data.Bitcoin.PaymentChannel.Types: [cpDustLimit] :: ChannelParameters -> BitcoinAmount
- Data.Bitcoin.PaymentChannel.Types: b64Encode :: Serialize a => a -> ByteString
- Data.Bitcoin.PaymentChannel.Types: defaultDustLimit :: BitcoinAmount
- Data.Bitcoin.PaymentChannel.Types: getChannelID :: PaymentChannel a => a -> OutPoint
+ Data.Bitcoin.PaymentChannel: recvPaymentForClose :: ReceiverPaymentChannel -> FullPayment -> Either PayChanError ReceiverPaymentChannel
+ Data.Bitcoin.PaymentChannel.Internal.State: isPastLockTimeDate :: UTCTime -> Config -> ChannelParameters -> Bool
+ Data.Bitcoin.PaymentChannel.Internal.State: pcsChannelFundingSource :: PaymentChannelState -> OutPoint
+ Data.Bitcoin.PaymentChannel.Types: ChannelExpired :: PayChanError
+ Data.Bitcoin.PaymentChannel.Types: ClosingPaymentBadValue :: PayChanError
+ Data.Bitcoin.PaymentChannel.Types: Config :: BitcoinAmount -> Hour -> Config
+ Data.Bitcoin.PaymentChannel.Types: [cDustLimit] :: Config -> BitcoinAmount
+ Data.Bitcoin.PaymentChannel.Types: [cSettlementPeriod] :: Config -> Hour
+ Data.Bitcoin.PaymentChannel.Types: data Config
+ Data.Bitcoin.PaymentChannel.Types: defaultConfig :: Config
+ Data.Bitcoin.PaymentChannel.Types: fundingAddress :: PaymentChannel a => a -> Address
+ Data.Bitcoin.PaymentChannel.Types: getChannelFunding :: PaymentChannel a => a -> OutPoint
+ Data.Bitcoin.PaymentChannel.Types: getFundingAmount :: PaymentChannel a => a -> BitcoinAmount
+ Data.Bitcoin.PaymentChannel.Types: getPaymentCount :: PaymentChannel a => a -> Word64
- Data.Bitcoin.PaymentChannel: channelFromInitialPayment :: ChannelParameters -> FundingTxInfo -> Address -> FullPayment -> Either PayChanError (BitcoinAmount, ReceiverPaymentChannel)
+ Data.Bitcoin.PaymentChannel: channelFromInitialPayment :: UTCTime -> Config -> ChannelParameters -> FundingTxInfo -> FullPayment -> Either PayChanError (BitcoinAmount, ReceiverPaymentChannel)
- Data.Bitcoin.PaymentChannel: channelWithInitialPaymentOf :: ChannelParameters -> FundingTxInfo -> (Hash256 -> Signature) -> Address -> BitcoinAmount -> (BitcoinAmount, FullPayment, SenderPaymentChannel)
+ Data.Bitcoin.PaymentChannel: channelWithInitialPaymentOf :: Config -> ChannelParameters -> FundingTxInfo -> (Hash256 -> Signature) -> Address -> BitcoinAmount -> (BitcoinAmount, FullPayment, SenderPaymentChannel)
- Data.Bitcoin.PaymentChannel: recvPayment :: ReceiverPaymentChannel -> FullPayment -> Either PayChanError (BitcoinAmount, ReceiverPaymentChannel)
+ Data.Bitcoin.PaymentChannel: recvPayment :: UTCTime -> ReceiverPaymentChannel -> FullPayment -> Either PayChanError (BitcoinAmount, ReceiverPaymentChannel)
- Data.Bitcoin.PaymentChannel.Internal.State: checkDustLimit :: ChannelParameters -> Payment -> Either PayChanError Payment
+ Data.Bitcoin.PaymentChannel.Internal.State: checkDustLimit :: Config -> Payment -> Either PayChanError Payment
- Data.Bitcoin.PaymentChannel.Internal.State: newPaymentChannelState :: ChannelParameters -> FundingTxInfo -> PaymentTxConfig -> PaymentSignature -> PaymentChannelState
+ Data.Bitcoin.PaymentChannel.Internal.State: newPaymentChannelState :: Config -> ChannelParameters -> FundingTxInfo -> PaymentTxConfig -> PaymentSignature -> PaymentChannelState
- Data.Bitcoin.PaymentChannel.Types: CChannelParameters :: SendPubKey -> RecvPubKey -> BitcoinLockTime -> BitcoinAmount -> ChannelParameters
+ Data.Bitcoin.PaymentChannel.Types: CChannelParameters :: SendPubKey -> RecvPubKey -> BitcoinLockTime -> ChannelParameters
- Data.Bitcoin.PaymentChannel.Types: class PaymentChannel a where getChannelID = pcsChannelID . getChannelState getExpirationDate = pcsExpirationDate . getChannelState getSenderPubKey = pcsClientPubKey . getChannelState senderChangeValue = pcsClientChangeVal . getChannelState channelValueLeft = channelValueLeft . getChannelState channelIsExhausted = channelIsExhausted . getChannelState expiresBefore expDate chan = getExpirationDate chan < expDate getNewestPayment pcs = pcsGetPayment (getChannelState pcs) getNewestSig = psSig . cpSignature . getNewestPayment
+ Data.Bitcoin.PaymentChannel.Types: class PaymentChannel a where getChannelFunding = pcsChannelFundingSource . getChannelState getExpirationDate = pcsExpirationDate . getChannelState getSenderPubKey = pcsClientPubKey . getChannelState getFundingAmount = pcsChannelTotalValue . getChannelState getPaymentCount = pcsPaymentCount . getChannelState fundingAddress = getP2SHFundingAddress . pcsParameters . getChannelState senderChangeValue = pcsClientChangeVal . getChannelState channelValueLeft = channelValueLeft . getChannelState channelIsExhausted = channelIsExhausted . getChannelState expiresBefore expDate chan = getExpirationDate chan < expDate getNewestPayment pcs = pcsGetPayment (getChannelState pcs) getNewestSig = psSig . cpSignature . getNewestPayment

Files

bitcoin-payment-channel.cabal view
@@ -1,5 +1,5 @@ name:                 bitcoin-payment-channel-version:              0.4.0.1+version:              0.5.0.0 synopsis:             Library for working with Bitcoin payment channels description:     A Bitcoin payment channel allows secure and instant transfer of bitcoins from one@@ -34,7 +34,6 @@   exposed-modules:      Data.Bitcoin.PaymentChannel                         Data.Bitcoin.PaymentChannel.Types                         Data.Bitcoin.PaymentChannel.Util-                        Data.Bitcoin.PaymentChannel.Movable                         -- Exposed for testing purposes                         Data.Bitcoin.PaymentChannel.Internal.State @@ -51,6 +50,7 @@                         Data.Bitcoin.PaymentChannel.Internal.Util    ghc-options:          -W+  default-language:     Haskell2010    build-depends:        base                >= 4.7    && < 5,                         haskoin-core        >= 0.4.0  && < 0.5.0,@@ -64,16 +64,18 @@                         time                >= 1.5.0  && < 1.7.0,                         aeson               >= 0.11.0 && < 0.12.0,                         scientific          >= 0.3.0  && < 0.4.0,-                        string-conversions  >= 0.4    && < 0.5+                        string-conversions  >= 0.4    && < 0.5,+                        tagged              >= 0.8    && < 1.0    hs-source-dirs:       src -  default-language:     Haskell2010 -executable Test-  main-is:              Main.hs--  ghc-options:          -W+test-suite test-bitcoin-payment-channel+  type: exitcode-stdio-1.0+  main-is: Main.hs+  default-language:     Haskell2010+  hs-source-dirs: test+  ghc-options:    -W    build-depends:        base         >= 4.7   && < 5,                         haskoin-core >= 0.4.0 && < 1.0.0,@@ -84,9 +86,5 @@                         time,                         cereal, aeson, -                        QuickCheck,+                        QuickCheck, test-framework, test-framework-quickcheck2,                         bitcoin-payment-channel--  hs-source-dirs:       test--  default-language:     Haskell2010
src/Data/Bitcoin/PaymentChannel.hs view
@@ -93,6 +93,7 @@      channelFromInitialPayment,     recvPayment,+    recvPaymentForClose,      getSettlementBitcoinTx,     getRefundBitcoinTx,@@ -102,9 +103,10 @@ where  import Data.Bitcoin.PaymentChannel.Internal.Types-    (PaymentTxConfig(..), Payment(..), FullPayment(..), pcsClientChangeVal)+    (PaymentTxConfig(..), Payment(..), FullPayment(..), Config,+    pcsConfig, pcsClientChangeVal, pcsParameters) import Data.Bitcoin.PaymentChannel.Internal.State-    (newPaymentChannelState, updatePaymentChannelState)+    (newPaymentChannelState, updatePaymentChannelState, isPastLockTimeDate) import qualified Data.Bitcoin.PaymentChannel.Internal.State as S import Data.Bitcoin.PaymentChannel.Internal.Payment     (createPayment, paymentFromState, verifyPaymentSigFromState)@@ -116,8 +118,10 @@ import Data.Bitcoin.PaymentChannel.Util (getFundingAddress) import Data.Bitcoin.PaymentChannel.Types -import qualified  Network.Haskoin.Crypto as HC-import qualified  Network.Haskoin.Transaction as HT+import qualified  Network.Haskoin.Crypto        as HC+import qualified  Network.Haskoin.Transaction   as HT+import            Data.Time.Clock                 (UTCTime(..))+import            Data.Time.Calendar              (Day(..))   -- |Create a new 'SenderPaymentChannel'.@@ -125,25 +129,26 @@ -- the channel and the funding transaction, as well as the value of the first payment. -- Returns a new 'SenderPaymentChannel' state object and the first channel payment. channelWithInitialPaymentOf ::-    ChannelParameters               -- ^ Specifies channel sender and receiver, channel expiration date and "dust limit"-    -> FundingTxInfo                -- ^ Holds information about the transaction used to fund the channel-    -> (HC.Hash256 -> HC.Signature) -- ^Used to sign payments from sender. When given a 'HC.Hash256', produces a signature that verifies against sender PubKey.+       Config                       -- ^ Various configuration options. 'defaultConfig' contains sensible defaults.+    -> ChannelParameters            -- ^ Specifies channel sender and receiver, channel expiration date and "dust limit"+    -> FundingTxInfo                -- ^ Holds information about the Bitcoin transaction used to fund the channel+    -> (HC.Hash256 -> HC.Signature) -- ^ Used to sign payments from sender. When given a 'HC.Hash256', produces a signature that verifies against sender PubKey.     -> HC.Address                   -- ^ Value sender/client change address-    -> BitcoinAmount                -- ^ Value of initial payment. Must be greater than or equal to 'minimumInitialPayment'+    -> BitcoinAmount                -- ^ Value of initial payment. Can be zero.     -> (BitcoinAmount, FullPayment, SenderPaymentChannel) -- ^Initial payment amount (may be capped), initial payment, and new sender state object-channelWithInitialPaymentOf cp fundInf signFunc sendAddr amount =+channelWithInitialPaymentOf cfg cp fundInf signFunc sendAddr amount =     let pConf = CPaymentTxConfig sendAddr         (CFullPayment (CPayment _ tmpSig) _ _ _) =             createPayment cp fundInf sendAddr amount signFunc     in         flip sendPayment amount $ CSenderPaymentChannel-            (newPaymentChannelState cp fundInf pConf tmpSig) signFunc+            (newPaymentChannelState cfg cp fundInf pConf tmpSig) signFunc  -- |Create new payment of specified value. sendPayment ::-    SenderPaymentChannel                -- ^Sender state object-    -> BitcoinAmount                    -- ^ Amount to send (the actual payment amount is capped so that that, at minimu, the client change value equals the dust limit)-    -> (BitcoinAmount, FullPayment, SenderPaymentChannel)  -- ^ Actual amount sent, payment, and updated sender state object+    SenderPaymentChannel                -- ^ Sender state object+    -> BitcoinAmount                    -- ^ Amount to send (the actual payment amount is capped)+    -> (BitcoinAmount, FullPayment, SenderPaymentChannel)  -- ^ Actual amount actually sent, payment, and updated sender state object sendPayment (CSenderPaymentChannel cs signFunc) amountToSend =     let         valSent = pcsClientChangeVal cs - newSenderValue@@ -175,39 +180,72 @@ -- about the payment channel, as well as the first channel payment -- produced by the sender. channelFromInitialPayment ::-    ChannelParameters -- ^ Specifies channel sender and receiver, plus channel expiration date-    -> FundingTxInfo -- ^ Holds information about the transaction used to fund the channel-    -> HC.Address   -- ^ Value sender/client change address-    -> FullPayment -- ^Initial channel payment+       UTCTime              -- ^ Current time. Needed for payment verification.+    -> Config               -- ^ Various configuration options. 'defaultConfig' contains sensible defaults.+    -> ChannelParameters    -- ^ Specifies channel sender and receiver, plus channel expiration date+    -> FundingTxInfo        -- ^ Holds information about the Bitcoin transaction used to fund the channel+    -> FullPayment          -- ^ Initial channel payment     -> Either PayChanError (BitcoinAmount, ReceiverPaymentChannel) -- ^Error or: value_received plus state object-channelFromInitialPayment cp fundInf sendAddr fp@(CFullPayment (CPayment _ sig) _ _ _) =+channelFromInitialPayment now cfg cp fundInf fp@(CFullPayment (CPayment _ sig) _ _ sendAddr) =         let             pConf = CPaymentTxConfig sendAddr         in-            flip recvPayment fp $ CReceiverPaymentChannel+            flip (recvPayment now) fp $ CReceiverPaymentChannel                 -- Create a new state with the unverified signature; then verify the same signature in 'recvPayment'-                (newPaymentChannelState cp fundInf pConf sig)+                (newPaymentChannelState cfg cp fundInf pConf sig)  -- |Register, on the receiving side, a payment made by 'sendPayment' on the sending side. -- Returns error if either the signature or payment amount is invalid, and otherwise -- the amount received with this 'Payment' and a new state object. recvPayment ::-    ReceiverPaymentChannel -- ^Receiver state object-    -> FullPayment -- ^Payment to verify and register+       UTCTime                  -- ^Current time. Needed for payment verification.+    -> ReceiverPaymentChannel   -- ^Receiver state object+    -> FullPayment              -- ^Payment to verify and register     -> Either PayChanError (BitcoinAmount, ReceiverPaymentChannel) -- ^Value received plus new receiver state object-recvPayment rpc@(CReceiverPaymentChannel oldState) fp@(CFullPayment paymnt _ _ _) =-    let-        verifySenderPayment hash pk sig = HC.verifySig hash sig (getPubKey pk)-    in-        if verifyPaymentSigFromState oldState verifySenderPayment paymnt then-            updatePaymentChannelState oldState fp >>=-            (\newState -> Right (-                S.channelValueLeft oldState - S.channelValueLeft newState-                , rpc { rpcState = newState })-            )-        else-            Left SigVerifyFailed+recvPayment currentTime rpc@(CReceiverPaymentChannel oldState) fp@(CFullPayment paymnt _ _ _)  =+    updatePaymentChannelState oldState fp >>=+    checkExpiration >>=+    verifyPaymentSignature paymnt >>=+    (\newState -> Right (+        S.channelValueLeft oldState - S.channelValueLeft newState+        , rpc { rpcState = newState })+    )+    where checkExpiration s =+            if isPastLockTimeDate currentTime (pcsConfig oldState) (pcsParameters oldState) then+                Left ChannelExpired+            else+                Right s +verifyPaymentSignature ::+    Payment                 -- ^Payment whose signature to verify+    -> PaymentChannelState  -- ^State object+    -> Either PayChanError PaymentChannelState+verifyPaymentSignature paymnt state =+    if verifyPaymentSigFromState state verifySenderPayment paymnt then+        Right state+    else+        Left SigVerifyFailed+    where verifySenderPayment hash pk sig = HC.verifySig hash sig (getPubKey pk)++-- |Same as 'recvPayment' but accept only a payment of zero value+--  with a new client change address. Used to produce the settlement+--  transaction that returns unsent funds to the client.+recvPaymentForClose ::+    ReceiverPaymentChannel  -- ^Receiver state object+    -> FullPayment          -- ^Payment to verify and register+    -> Either PayChanError ReceiverPaymentChannel -- ^ Receiver state object+recvPaymentForClose (CReceiverPaymentChannel state) fp =+    recvPayment futureTimeStamp newAddressState fp >>=+        \(amtRecv, newState) -> case amtRecv of+            0 -> Right newState+            _ -> Left ClosingPaymentBadValue+    where newAddressState = CReceiverPaymentChannel $+            S.setClientChangeAddress state (fpChangeAddr fp)+          -- When receiving a payment of zero value, which only modifies the+          -- client change address, we don't care about verifying that the channel+          -- hasn't expired yet (which it may well have, since the client wants its+          -- money back)+          futureTimeStamp = UTCTime (ModifiedJulianDay 94183) 0     -- 100 years into the future from now  -- |The value transmitted over the channel is settled when this transaction is in the Blockchain. -- The receiver will want to make sure a transaction produced by this function
src/Data/Bitcoin/PaymentChannel/Internal/Bitcoin/LockTime.hs view
@@ -32,7 +32,7 @@     | i >=  500000000 = LockTimeDate $ posixSecondsToUTCTime (fromIntegral i)     | otherwise       = error "GHC bug?" --- | Convert to Bitcoin format ('Word32')+-- | Convert to Bitcoin format (uint32 UNIX timestamp) toWord32 :: BitcoinLockTime -> Word32 toWord32 (LockTimeBlockHeight i) = i toWord32 (LockTimeDate date) =
src/Data/Bitcoin/PaymentChannel/Internal/Bitcoin/Script.hs view
@@ -65,7 +65,7 @@ getP2SHFundingAddress = scriptToP2SHAddress . getRedeemScript  getRedeemScript :: ChannelParameters -> Script-getRedeemScript (CChannelParameters senderPK recvrPK lockTime _) =+getRedeemScript (CChannelParameters senderPK recvrPK lockTime) =     paymentChannelRedeemScript senderPK recvrPK lockTime  getRedeemScriptBS :: ChannelParameters -> B.ByteString
src/Data/Bitcoin/PaymentChannel/Internal/Error.hs view
@@ -13,7 +13,9 @@     ChangeAddrMismatch Address |     RedeemScriptMismatch Script |     DustOutput BitcoinAmount |-    PartialPaymentBadValue BitcoinAmount+    PartialPaymentBadValue BitcoinAmount |+    ClosingPaymentBadValue |+    ChannelExpired         deriving Generic  instance Show PayChanError where@@ -29,3 +31,6 @@         " was accepted, expecting second payment change value to match that."     show (DustOutput limit) = "server dust limit of " ++ show limit ++         " not respected by client change output"+    show ClosingPaymentBadValue = "payment not of zero value." +++        " cannot receive value in closing payment."+    show ChannelExpired = "channel too close to expiration date"
src/Data/Bitcoin/PaymentChannel/Internal/Payment.hs view
@@ -27,8 +27,8 @@   }  fromState :: PaymentChannelState -> UnsignedPayment-fromState (CPaymentChannelState cp (CFundingTxInfo hash idx fundingVal)-          (CPaymentTxConfig changeAddr) changeValue _) =+fromState (CPaymentChannelState _ cp (CFundingTxInfo hash idx fundingVal)+          (CPaymentTxConfig changeAddr) _ changeValue _) =     UnsignedPayment         (HT.OutPoint hash idx) fundingVal         (getRedeemScript cp)   changeAddr changeValue@@ -63,7 +63,7 @@     -> BitcoinAmount                -- ^ sender change value (subtract 'n' from current sender change value to get payment of value 'n')     -> (HC.Hash256 -> HC.Signature) -- ^ signing function     -> FullPayment-paymentFromState (CPaymentChannelState cp fti (CPaymentTxConfig changeAddr) _ _) =+paymentFromState (CPaymentChannelState _ cp fti (CPaymentTxConfig changeAddr) _ _ _) =     createPayment cp fti changeAddr  -- |Create payment, pure.@@ -114,7 +114,7 @@     -> (HC.Hash256 -> SendPubKey -> HC.Signature -> Bool)     -> Payment     -> Bool-verifyPaymentSigFromState (CPaymentChannelState cp fti (CPaymentTxConfig changeAddr) _ _) =+verifyPaymentSigFromState (CPaymentChannelState _ cp fti (CPaymentTxConfig changeAddr) _ _ _) =     verifyPaymentSig cp fti changeAddr (cpSenderPubKey cp) ---Payment create/verify--- 
src/Data/Bitcoin/PaymentChannel/Internal/Refund.hs view
@@ -11,7 +11,7 @@ import qualified Network.Haskoin.Script as HS  getUnsignedRefundTx :: PaymentChannelState -> BitcoinAmount -> HT.Tx-getUnsignedRefundTx st txFee = -- @CPaymentChannelState =+getUnsignedRefundTx st txFee =     let         baseTx = toUnsignedBitcoinTx $ fromState st --create empty payment tx, which redeems funding tx         refundOut = HT.TxOut@@ -34,7 +34,7 @@     PaymentChannelState     -> BitcoinAmount -- ^Bitcoin transaction fee     -> HC.Hash256-getRefundTxHashForSigning pcs@(CPaymentChannelState cp _ _ _ _) newValueLeft =+getRefundTxHashForSigning pcs@(CPaymentChannelState _ cp _ _ _ _ _) newValueLeft =         HS.txSigHash tx (getRedeemScript cp) 0 (HS.SigAll False)             where tx = getUnsignedRefundTx pcs newValueLeft @@ -43,7 +43,7 @@     -> BitcoinAmount      -- ^Bitcoin tx fee     -> HC.Signature     -- ^Signature over 'getUnsignedRefundTx' which verifies against clientPubKey     -> FinalTx-refundTxAddSignature pcs@(CPaymentChannelState cp _ _ _ _) txFee clientRawSig =+refundTxAddSignature pcs@(CPaymentChannelState _ cp _ _ _ _ _) txFee clientRawSig =         let             inputScript = getP2SHInputScript cp $ refundTxScriptSig clientRawSig         in
src/Data/Bitcoin/PaymentChannel/Internal/Serialization.hs view
@@ -22,7 +22,7 @@ import           Data.Word (Word64) import           Data.EitherR (fmapL) import           Data.Typeable-import           Debug.Trace+import qualified Data.Tagged as Tag  -- Generic PayChanError instance instance Bin.Serialize PayChanError@@ -113,16 +113,16 @@ deriving instance Bin.Serialize RecvPubKey  instance Bin.Serialize PaymentChannelState where-    put (CPaymentChannelState par fti payConf valLeft sig) =-        Bin.put par >> Bin.put fti >> Bin.put payConf >>+    put (CPaymentChannelState cfg par fti payConf payCount valLeft sig) =+        Bin.put cfg >> Bin.put par >> Bin.put fti >> Bin.put payConf >> Bin.put payCount >>         Bin.put valLeft >> Bin.put sig     get = CPaymentChannelState <$> Bin.get <*> Bin.get <*>-        Bin.get <*> Bin.get <*> Bin.get+        Bin.get <*> Bin.get <*> Bin.get <*> Bin.get <*> Bin.get  instance Bin.Serialize ChannelParameters where-    put (CChannelParameters pks pkr lt dustLimit) =-        Bin.put pks >> Bin.put pkr >> Bin.put lt >> Bin.put dustLimit-    get = CChannelParameters <$> Bin.get <*> Bin.get <*> Bin.get <*> Bin.get+    put (CChannelParameters pks pkr lt) =+        Bin.put pks >> Bin.put pkr >> Bin.put lt+    get = CChannelParameters <$> Bin.get <*> Bin.get <*> Bin.get  instance Bin.Serialize FundingTxInfo where     put (CFundingTxInfo h idx val) =@@ -133,6 +133,11 @@     put (CPaymentTxConfig sendAddr) =         Bin.put sendAddr     get = CPaymentTxConfig <$> Bin.get++instance Bin.Serialize Config where+    put (Config dl sp) =+        Bin.put dl >> Bin.put (Tag.unTagged sp)+    get = Config <$> Bin.get <*> fmap Tag.Tagged Bin.get  instance Bin.Serialize Payment where     put (CPayment val sig) =
src/Data/Bitcoin/PaymentChannel/Internal/Settlement.hs view
@@ -14,7 +14,7 @@ serverSigHash = HS.SigAll False  csFromState :: PaymentChannelState -> ClientSignedPayment-csFromState cs@(CPaymentChannelState _ _ _ _ clientSig) =+csFromState cs@(CPaymentChannelState _ _ _ _ _ _ clientSig) =     ClientSignedPayment (fromState cs) clientSig  toUnsignedSettlementTx :: ClientSignedPayment -> HC.Address -> BitcoinAmount -> HT.Tx@@ -48,7 +48,7 @@     -> HC.Address    -- ^Receiver destination address     -> BitcoinAmount -- ^Bitcoin transaction fee     -> HC.Hash256-settlementSigningTxHashFromState cs@(CPaymentChannelState cp _ _ _ _) =+settlementSigningTxHashFromState cs@(CPaymentChannelState _ cp _ _ _ _ _) =     getSettlementTxHashForSigning (csFromState cs) cp  getSignedSettlementTx ::@@ -73,6 +73,6 @@     -> HC.Address       -- ^Receiver/server funds destination address     -> BitcoinAmount    -- ^Bitcoin tx fee     -> HT.Tx-signedSettlementTxFromState cs@(CPaymentChannelState cp _ _ _ _) signFunc recvAddr txFee =+signedSettlementTxFromState cs@(CPaymentChannelState _ cp _ _ _ _ _) signFunc recvAddr txFee =     getSignedSettlementTx (csFromState cs) cp recvAddr txFee serverSig         where serverSig = signFunc (settlementSigningTxHashFromState cs recvAddr txFee)
src/Data/Bitcoin/PaymentChannel/Internal/State.hs view
@@ -10,32 +10,34 @@ import qualified Network.Haskoin.Transaction as HT import qualified Network.Haskoin.Crypto as HC import qualified Network.Haskoin.Script as HS+import           Data.Time.Clock + pcsChannelTotalValue = ftiOutValue . pcsFundingTxInfo pcsValueTransferred cs = pcsChannelTotalValue cs - pcsClientChangeVal cs pcsChannelValueLeft = pcsClientChangeVal pcsClientPubKey = cpSenderPubKey . pcsParameters pcsServerPubKey = cpReceiverPubKey . pcsParameters-pcsDustLimit = cpDustLimit . pcsParameters+pcsDustLimit = cDustLimit . pcsConfig pcsExpirationDate = cpLockTime . pcsParameters pcsClientChangeAddress = ptcSenderChangeAddress . pcsPaymentConfig pcsClientChangeScriptPubKey = addressToScriptPubKeyBS . pcsClientChangeAddress pcsLockTime = cpLockTime . pcsParameters-pcsPrevOut (CPaymentChannelState _ (CFundingTxInfo h i _) _ _ _) = OutPoint h i+pcsPrevOut (CPaymentChannelState _ _ (CFundingTxInfo h i _) _ _ _ _) = OutPoint h i -pcsChannelID :: PaymentChannelState -> HT.OutPoint-pcsChannelID pcs = HT.OutPoint (ftiHash fti) (ftiOutIndex fti)+pcsChannelFundingSource :: PaymentChannelState -> HT.OutPoint+pcsChannelFundingSource pcs = HT.OutPoint (ftiHash fti) (ftiOutIndex fti)     where fti = pcsFundingTxInfo pcs  pcsGetPayment :: PaymentChannelState -> Payment-pcsGetPayment (CPaymentChannelState _ _ _ val sig) = CPayment val sig+pcsGetPayment (CPaymentChannelState _ _ _ _ _ val sig) = CPayment val sig  -- |Set new client/sender change address. -- Use this function if the client wishes to change its change address. -- First set the new change address using this function, then accept the payment which -- uses this new change address. setClientChangeAddress :: PaymentChannelState -> HC.Address -> PaymentChannelState-setClientChangeAddress pcs@(CPaymentChannelState _ _ pConf _ _) addr =+setClientChangeAddress pcs@(CPaymentChannelState _ _ _ pConf _ _ _) addr =     pcs { pcsPaymentConfig = newPayConf }         where newPayConf = pConf { ptcSenderChangeAddress = addr } @@ -47,7 +49,7 @@ --  This avoids creating a Bitcoin transaction that won't circulate --  in the Bitcoin P2P network. channelValueLeft :: PaymentChannelState -> BitcoinAmount-channelValueLeft pcs@(CPaymentChannelState (CChannelParameters _ _ _ dustLimit) _ _ _ _)   =+channelValueLeft pcs@(CPaymentChannelState (Config dustLimit _) _ _ _ _ _ _)   =     pcsClientChangeVal pcs - dustLimit  -- |Returns 'True' if all available channel value has been transferred, 'False' otherwise@@ -56,22 +58,25 @@     psSigHash (pcsPaymentSignature pcs) == HS.SigNone True ||         channelValueLeft pcs == 0 -newPaymentChannelState channelParameters fundingTxInfo paymentConfig paySig =+newPaymentChannelState cfg channelParameters fundingTxInfo paymentConfig paySig =     CPaymentChannelState {+        pcsConfig               = cfg,         pcsParameters           = channelParameters,         pcsFundingTxInfo        = fundingTxInfo,         pcsPaymentConfig        = paymentConfig,-        pcsClientChangeVal            = ftiOutValue fundingTxInfo,+        pcsPaymentCount         = 0,+        pcsClientChangeVal      = ftiOutValue fundingTxInfo,         pcsPaymentSignature     = paySig     } + -- |Update state with verified payment. updatePaymentChannelState  ::     PaymentChannelState     -> FullPayment     -> Either PayChanError PaymentChannelState-updatePaymentChannelState (CPaymentChannelState cp fun@(CFundingTxInfo h i _)-                          pconf@(CPaymentTxConfig addr) oldSenderVal _)+updatePaymentChannelState (CPaymentChannelState cfg cp fun@(CFundingTxInfo h i _)+                          pconf@(CPaymentTxConfig addr) payCount oldSenderVal _)     (CFullPayment payment@(CPayment newSenderVal _) payOP payScript payChgAddr)         | (HT.outPointHash payOP /= h) || (HT.outPointIndex payOP /= i) =             Left $ OutPointMismatch $ OutPoint h i@@ -79,13 +84,24 @@             Left $ ChangeAddrMismatch addr         | payScript /= getRedeemScript cp =             Left $ RedeemScriptMismatch $ getRedeemScript cp-        | newSenderVal <= oldSenderVal =-            CPaymentChannelState cp fun pconf newSenderVal . cpSignature <$>-                checkDustLimit cp payment-        | otherwise = Left $ BadPaymentValue (newSenderVal - oldSenderVal)+        | newSenderVal > oldSenderVal =+            Left $ BadPaymentValue (newSenderVal - oldSenderVal)+        | otherwise =+            CPaymentChannelState cfg cp fun pconf (payCount+1) newSenderVal . cpSignature <$>+                checkDustLimit cfg payment -checkDustLimit :: ChannelParameters -> Payment -> Either PayChanError Payment-checkDustLimit (CChannelParameters _ _ _ dustLimit) payment@(CPayment senderChangeVal _)+checkDustLimit :: Config -> Payment -> Either PayChanError Payment+checkDustLimit (Config dustLimit _) payment@(CPayment senderChangeVal _)     | senderChangeVal < dustLimit =         Left $ DustOutput dustLimit     | otherwise = Right payment++isPastLockTimeDate :: UTCTime -> Config -> ChannelParameters -> Bool+isPastLockTimeDate currentTime (Config _ settlePeriodHrs) (CChannelParameters _ _ (LockTimeDate expTime)) =+    currentTime > (settlePeriod `addUTCTime` expTime)+        where settlePeriod = -1 * fromIntegral (toSeconds settlePeriodHrs) :: NominalDiffTime+isPastLockTimeDate _ _ (CChannelParameters _ _ (LockTimeBlockHeight _)) =+    True+    -- We don't have the current Bitcoin block so we regard the channel as expired,+    -- in order to make we don't accept block count-based locktimes for now.+
src/Data/Bitcoin/PaymentChannel/Internal/Types.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveGeneric, DataKinds #-}  module Data.Bitcoin.PaymentChannel.Internal.Types (@@ -24,21 +24,27 @@ import qualified Network.Haskoin.Script as HS import           Data.Typeable import           Data.Word-import           GHC.Generics+import qualified Data.Tagged as Tag ++defaultConfig = Config defaultDustLimit defaultSettlementPeriod+ defaultDustLimit = 700 :: BitcoinAmount-defaultMinChanSize = defaultDustLimit * 2+defaultSettlementPeriod = 10 :: Hour  newtype UnsignedPaymentTx = CUnsignedPaymentTx { unsignedTx :: HT.Tx } deriving Show type FinalTx = HT.Tx  -- |Shared state object used by both value sender and value receiver. data PaymentChannelState = CPaymentChannelState {+    -- |Holds various optional configuration options+    pcsConfig               ::  Config,     -- |Defined by the sender and receiver     pcsParameters           ::  ChannelParameters,     -- |Retrieved by looking at the in-blockchain funding transaction     pcsFundingTxInfo        ::  FundingTxInfo,     pcsPaymentConfig        ::  PaymentTxConfig,+    pcsPaymentCount         ::  Word64,     -- |Client change value     pcsClientChangeVal      ::  BitcoinAmount,     -- |Signature over payment transaction of value 'pcsValueLeft'@@ -50,13 +56,7 @@     cpSenderPubKey      ::  SendPubKey,     cpReceiverPubKey    ::  RecvPubKey,     -- |Channel expiration date/time-    cpLockTime          ::  BitcoinLockTime,-    -- |Use a per-channel dust limit, such that when the remaining channel value-    --  hits this limit (rather than zero), we say the channel is exhausted.-    --  This avoids the relatively complex SigSingle/SigNone logic, and reduces-    --  a payment to just a ANYONECANPAY|SigSingle signature with a corresponding-    --  change output, which is set to the channel funding address.-    cpDustLimit         ::  BitcoinAmount+    cpLockTime          ::  BitcoinLockTime } deriving (Eq, Show, Typeable)  -- |Holds information about the Bitcoin transaction used to fund@@ -73,7 +73,17 @@     ptcSenderChangeAddress  ::  HC.Address } deriving (Eq, Show, Typeable) --- |Used to transfer value from sender to receiver.+-- |Miscellaneous configuration options+data Config = Config {+    -- | Refuse to accept/produce payments with a client change value less than this amount.+    cDustLimit          :: BitcoinAmount+    -- | This many hours before the channel expiration date, consider the channel closed.+    --  This gives the receiver time to publish the settlement transaction, before the+    --  refund transaction becomes valid.+  , cSettlementPeriod   :: Hour+} deriving (Eq, Show, Typeable)++-- |Contains the bare minimum of information to transfer value from sender to receiver. data Payment = CPayment {     -- |Channel value remaining ('pcsValueLeft' of the state from which this payment was made)     cpClientChange :: BitcoinAmount@@ -94,18 +104,14 @@ -- |Contains payment signature plus sig hash flag byte data PaymentSignature = CPaymentSignature {     psSig       ::  HC.Signature-    -- |SigHash flag. Denotes whether the sender still wants its change output-    --  included in the settling transaction. In case of (SigNone True), the-    --  value sender has given up the rest of the channel value, leaving-    --  everything to the receiver. This is necessary so that no settling-    --  transaction containing an output below the "dust limit" is produced.+    -- |SigHash flag. Always "SigSingle True".     ,psSigHash  ::  HS.SigHash } deriving (Eq, Show, Typeable)  -- |Wraps a Network.Haskoin.Script.Script newtype ChanScript = ChanScript { getScript :: HS.Script } deriving (Eq, Show) --- Never confuse sender/receiver pubkey again: let compiler check+-- Never confuse sender/receiver pubkey newtype SendPubKey = MkSendPubKey {     getSenderPK        ::  HC.PubKey } deriving (Eq, Show)@@ -121,3 +127,7 @@     getPubKey = getSenderPK instance IsPubKey RecvPubKey where     getPubKey = getReceiverPK++type Hour = Tag.Tagged "Hour" Word32+toSeconds :: Hour -> Integer+toSeconds = fromIntegral . (* 3600) . Tag.unTagged
− src/Data/Bitcoin/PaymentChannel/Movable.hs
@@ -1,190 +0,0 @@-{-|-Module      : Data.Bitcoin.PaymentChannel.Movable-Description : Bitcoin payment channel library-License     : PublicDomain-Maintainer  : runesvend@gmail.com-Stability   : experimental-Portability : POSIX--TODO--}--module Data.Bitcoin.PaymentChannel.Movable where--import           Data.Bitcoin.PaymentChannel-import           Data.Bitcoin.PaymentChannel.Types-import           Data.Bitcoin.PaymentChannel.Internal.Types-import qualified Data.Bitcoin.PaymentChannel.Internal.State as S-import qualified Network.Haskoin.Crypto as HC-import qualified Data.Serialize         as Bin-import           Control.Applicative    ((<|>))---- |A ReceiverPaymentChannel whose received value can be redeemed while---  keeping the channel open, by switching between two different OutPoints---  in the FundingTxInfo.-data MovableChan =-      Settled   {-        beginVal    :: BitcoinAmount-      , beginState  :: ReceiverPaymentChannel-  } | Unsettled {-        channels    ::  ChannelPair-      -- If payment 1/2 has been accepted, accept 2/2 with 'receiveSecondPayment pp'-      , finishPay   ::  Maybe PartialPayment-  } -- deriving (Show)--data ChannelPair = ChannelPair {-    beginVal'   ::  BitcoinAmount-  , oldState    ::  ReceiverPaymentChannel-  , newState    ::  ReceiverPaymentChannel-}---- |We wrap the state in which only payment 1/2 has been received in a data---  type, in order to be able to serialize it to disk.-data PartialPayment =-    NewNeedsUpdate ChannelPair BitcoinAmount-  | OldNeedsUpdate ChannelPair BitcoinAmount--instance PaymentChannel MovableChan where-    valueToMe = channelValueLeft . fst . getStateForClosing-    getChannelState = rpcState . fst . getStateForClosing-    _setChannelState (Settled v rpc) s = Settled v (rpc { rpcState = s })-    _setChannelState (Unsettled (ChannelPair v old new) fp) s =-        Unsettled (ChannelPair v (setState s old) (setState s new)) fp-            where setState s rpc = rpc { rpcState = s }--newMovableChan ::-    ChannelParameters-    -> FundingTxInfo-    -> FullPayment-    -> Either PayChanError (BitcoinAmount, MovableChan, BitcoinAmount)-newMovableChan cp fti@(CFundingTxInfo _ _ chanVal)-               fullPayment@(CFullPayment _ _ _ payChgAddr) =-    checkChangeAddr >>= channelFromInitialPayment cp fti desiredChangeAddr >>=-        \(payAmount, rpc) -> Right (payAmount, Settled chanVal rpc, channelValueLeft rpc)-    where desiredChangeAddr = getFundingAddress cp-          checkChangeAddr   =-                if payChgAddr /= desiredChangeAddr then-                    Left $ ChangeAddrMismatch desiredChangeAddr-                else-                    Right fullPayment--getStateForClosing :: MovableChan -> (ReceiverPaymentChannel,BitcoinAmount)-getStateForClosing (Settled v rpc) = (rpc,v)-getStateForClosing (Unsettled (ChannelPair v _ newRpc) _) = (newRpc,v)--getStateByInfo :: MovableChan -> BitcoinLockTime -> OutPoint -> Maybe (ReceiverPaymentChannel,BitcoinAmount)-getStateByInfo mc lt op = case mc of-    (Settled v rpc)                       -> checkInfo rpc v-    (Unsettled (ChannelPair v old new) _) -> checkInfo new v <|> checkInfo old v-    where checkInfo rpc v =-            if  getChannelID      rpc == op &&-                getExpirationDate rpc == lt then-                    Just (rpc,v)-                else-                    Nothing--moveChannel ::-    MovableChan-    -> (HC.Hash256 -> HC.Signature) -- ^ Function which produces a signature which verifies against 'cpReceiverPubKey'-    -> HC.Address                   -- ^ Receiver destination address. Funds sent over the channel will be sent to this address, the rest back to the client change address (an argument to 'channelWithInitialPaymentOf').-    -> BitcoinAmount                -- ^ Bitcoin transaction fee-    -> Maybe (Tx, MovableChan)-moveChannel Unsettled{} _ _ _ = Nothing-moveChannel (Settled v rpc) signFunc destAddr txFee =-    -- If we haven't received any value, moving the channel makes no sense-    if valueToMe rpc > 0 then-        Just (settleTx, Unsettled (ChannelPair v rpc newRpc) Nothing)-    else-        Nothing-    where settleTx = getSettlementBitcoinTx rpc signFunc destAddr txFee-          newRpc = CReceiverPaymentChannel $ S.setFundingSource (getChannelState rpc) fti-          fti = CFundingTxInfo (txHash settleTx)-                -- Client output always at index zero-                0 (senderChangeValue rpc)--markAsSettled ::-    MovableChan-    -> Maybe MovableChan-markAsSettled Settled{} = Nothing-markAsSettled (Unsettled _ (Just _)) = Nothing-markAsSettled (Unsettled (ChannelPair v _ newRpc) Nothing) =-    if valueToMe newRpc > 0 then-        Just $ Settled v newRpc-    else-        Nothing--recvSinglePayment ::-    MovableChan-    -> FullPayment-    -> Either PayChanError (BitcoinAmount, MovableChan, BitcoinAmount)-recvSinglePayment (Settled v rpc) fp = recvPayment rpc fp >>=-    \(a, newRpc) -> Right (a, Settled v newRpc, channelValueLeft newRpc)--- Accept payment 1/2-recvSinglePayment (Unsettled cp@(ChannelPair _ old new) Nothing) fp@(CFullPayment _ op _ _)-    | S.pcsPrevOut (getChannelState old) == op = oldRecvPay cp fp-    | S.pcsPrevOut (getChannelState new) == op = newRecvPay cp fp-    | otherwise = Left . OutPointMismatch . S.pcsPrevOut . getChannelState $ new--- If we've already received paymed 1/2, we receive 2/2 with this-recvSinglePayment (Unsettled _ (Just pp)) fp = receiveSecondPayment pp fp--oldRecvPay ::-    ChannelPair-    -> FullPayment-    -> Either PayChanError (BitcoinAmount, MovableChan, BitcoinAmount)-oldRecvPay (ChannelPair v old new) fp = recvPayment old fp >>=-    -- Update old state, and leave behind a function that updates the new state-    \(amt, newOld) -> Right-            (amt, Unsettled (ChannelPair v newOld new)-            (Just $ NewNeedsUpdate (ChannelPair v newOld new) amt),-            channelValueLeft newOld)--newRecvPay ::-    ChannelPair-    -> FullPayment-    -> Either PayChanError (BitcoinAmount, MovableChan, BitcoinAmount)-newRecvPay (ChannelPair v old new) fp = recvPayment new fp >>=-    -- Update new state, and leave behind a function that updates the old state-     \(amt, newNew) -> Right-            (amt, Unsettled (ChannelPair v old newNew)-            (Just $ OldNeedsUpdate (ChannelPair v old newNew) amt),-            channelValueLeft newNew)--receiveSecondPayment ::-    PartialPayment-    -> FullPayment-    -> Either PayChanError (BitcoinAmount, MovableChan, BitcoinAmount)-receiveSecondPayment (OldNeedsUpdate cp amt) fp =-    checkChangeValueMatch amt fp >>= oldRecvPay cp >>=-        \(amt, mc, vLeft) -> Right (amt, mc { finishPay = Nothing }, vLeft)-receiveSecondPayment (NewNeedsUpdate cp amt) fp =-    checkChangeValueMatch amt fp >>= newRecvPay cp >>=-        \(amt, mc, vLeft) -> Right (amt, mc { finishPay = Nothing }, vLeft)---- | We want to make sure that payment 1/2 and 2/2 are of equal value-checkChangeValueMatch :: BitcoinAmount -> FullPayment -> Either PayChanError FullPayment-checkChangeValueMatch firstPayVal fp@(CFullPayment (CPayment val _) _ _ _) =-    if val /= firstPayVal then Left $ PartialPaymentBadValue firstPayVal else Right fp---instance Bin.Serialize MovableChan where-    put (Settled bVal rpc) =-        Bin.putWord8 0x01 >> Bin.put bVal >> Bin.put rpc-    put (Unsettled cPair mPP) =-        Bin.putWord8 0x02 >> Bin.put cPair >> Bin.put mPP-    get = Bin.getWord8 >>= \w -> case w of-        0x01 -> Settled <$> Bin.get <*> Bin.get-        0x02 -> Unsettled <$> Bin.get <*> Bin.get-        n    -> fail $ "unknown start byte: " ++ show n--instance Bin.Serialize ChannelPair where-    put (ChannelPair bVal old new) =-        Bin.put bVal >> Bin.put old >> Bin.put new-    get = ChannelPair <$> Bin.get <*> Bin.get <*> Bin.get--instance Bin.Serialize PartialPayment where-    put (NewNeedsUpdate cp amt) = Bin.putWord8 0x01 >> Bin.put cp >> Bin.put amt-    put (OldNeedsUpdate cp amt) = Bin.putWord8 0x02 >> Bin.put cp >> Bin.put amt-    get = Bin.getWord8 >>= \w -> case w of-            0x01 -> NewNeedsUpdate <$> Bin.get <*> Bin.get-            0x02 -> OldNeedsUpdate <$> Bin.get <*> Bin.get-            n    -> fail $ "unknown start byte: " ++ show n
src/Data/Bitcoin/PaymentChannel/Types.hs view
@@ -12,50 +12,49 @@  module Data.Bitcoin.PaymentChannel.Types (-PaymentChannel(..),-SenderPaymentChannel(..),-ReceiverPaymentChannel(..),-Payment,cpSignature,-FullPayment(..),-FundingTxInfo(..),-ChannelParameters(..),-PayChanError(..),-PaymentChannelState,-SendPubKey(..),RecvPubKey(..),IsPubKey(..),-BitcoinAmount,-BitcoinLockTime(..), fromDate,-usesBlockHeight,---- Util-b64Encode,--- Constants-defaultDustLimit,+    PaymentChannel(..),+    SenderPaymentChannel(..),+    ReceiverPaymentChannel(..),+    Config(..),defaultConfig,+    Payment,cpSignature,+    FullPayment(..),+    FundingTxInfo(..),+    ChannelParameters(..),+    PayChanError(..),+    PaymentChannelState,+    SendPubKey(..),RecvPubKey(..),IsPubKey(..),+    BitcoinAmount,+    BitcoinLockTime(..), fromDate,+    usesBlockHeight ) where  import Data.Bitcoin.PaymentChannel.Internal.Types-import Data.Bitcoin.PaymentChannel.Internal.Serialization+import Data.Bitcoin.PaymentChannel.Internal.Serialization ()  import qualified Data.Bitcoin.PaymentChannel.Internal.State as S+import qualified Data.Bitcoin.PaymentChannel.Internal.Bitcoin.Script as Script import Data.Bitcoin.PaymentChannel.Internal.Error (PayChanError(..))  import qualified  Data.Serialize as Bin import            Data.Aeson as JSON -- (FromJSON, ToJSON) import qualified  Network.Haskoin.Crypto as HC import qualified  Network.Haskoin.Transaction as HT--- import qualified  Network.Haskoin.Transaction as HS+import            Data.Word (Word64)   -- |Get various information about an open payment channel. class PaymentChannel a where-    -- |Get value sent to receiver/left for sender+    -- |Get amount received by receiver/left for sender     valueToMe           :: a -> BitcoinAmount     -- |Retrieve internal state object     getChannelState     :: a -> PaymentChannelState--    getChannelID        :: a -> HT.OutPoint+    getChannelFunding   :: a -> HT.OutPoint     getExpirationDate   :: a -> BitcoinLockTime     getSenderPubKey     :: a -> SendPubKey+    getFundingAmount    :: a -> BitcoinAmount+    getPaymentCount     :: a -> Word64+    fundingAddress      :: a -> HC.Address     getNewestPayment    :: a -> Payment     getNewestSig        :: a -> HC.Signature     -- |Return True if channel expires earlier than given expiration date@@ -68,9 +67,12 @@     -- |Returns 'True' if all available channel value has been transferred, 'False' otherwise     channelIsExhausted  :: a -> Bool -    getChannelID       = S.pcsChannelID . getChannelState+    getChannelFunding       = S.pcsChannelFundingSource . getChannelState     getExpirationDate  = S.pcsExpirationDate . getChannelState     getSenderPubKey    = S.pcsClientPubKey . getChannelState+    getFundingAmount   = S.pcsChannelTotalValue . getChannelState+    getPaymentCount    = pcsPaymentCount . getChannelState+    fundingAddress  = Script.getP2SHFundingAddress . pcsParameters . getChannelState     senderChangeValue  = pcsClientChangeVal . getChannelState     channelValueLeft   = S.channelValueLeft . getChannelState     channelIsExhausted = S.channelIsExhausted . getChannelState
test/Main.hs view
@@ -17,16 +17,120 @@ import qualified Data.Serialize     as Bin import           Data.Typeable +import           Data.Time.Clock                 (UTCTime(..))+import           Data.Time.Calendar              (Day(..))+ import Test.QuickCheck-import Debug.Trace+import Test.Framework (Test, testGroup, defaultMain)+import Test.Framework.Providers.QuickCheck2 (testProperty) -dUST_LIMIT = 700 :: BitcoinAmount-mIN_CHANNEL_SIZE = 1400 :: BitcoinAmount+-- |We don't bother testing timestamps for now+nowishTimestamp :: UTCTime+nowishTimestamp = UTCTime (ModifiedJulianDay 57683) 0     -- 2016-10-22 -testAddrTestnet = "2N414xMNQaiaHCT5D7JamPz7hJEc9RG7469" :: HC.Address-testAddrLivenet = "14wjVnwHwMAXDr6h5Fw38shCWUB6RSEa63" :: HC.Address+mIN_CHANNEL_SIZE :: BitcoinAmount+mIN_CHANNEL_SIZE = cDustLimit defaultConfig * 2 +testAddrTestnet :: HC.Address+testAddrTestnet = "2N414xMNQaiaHCT5D7JamPz7hJEc9RG7469"+testAddrLivenet :: HC.Address+testAddrLivenet = "14wjVnwHwMAXDr6h5Fw38shCWUB6RSEa63" ++main :: IO ()+main = defaultMain tests++tests :: [Test]+tests =+    [ testGroup "Payment"+        [ testProperty "Sender value in settlement tx" $+            testPaymentSession checkSenderValue+        , testProperty "Receiver value in settlement tx" $+            testPaymentSession checkReceiverValue+        , testProperty "Sender/receiver state match" $+            testPaymentSession checkSendRecvStateMatch+        , testProperty "Sent amount == received amount" $+            testPaymentSession checkRecvSendAmount+        ]+    , testGroup "Serialization"+        [ testProperty "FullPayment JSON"   (jsonSerDeser :: FullPayment -> Bool)+        , testProperty "FullPayment Binary" testPaymentBin+        -- Fails: https://github.com/haskoin/haskoin/issues/287+        -- , testProperty "ChanScript ser/deser" testScriptBin+        ]+    ]++testPaymentJSON :: FullPayment -> Bool+testPaymentJSON = jsonSerDeser+testPaymentBin :: FullPayment -> Bool+testPaymentBin = binSerDeser+testScriptBin :: ChanScript -> Bool -- Fails: https://github.com/haskoin/haskoin/issues/287+testScriptBin = binSerDeser++checkSenderValue :: (ArbChannelPair, [BitcoinAmount]) -> Bool+checkSenderValue (ArbChannelPair _ recvChan amountSent _ recvSignFunc, _) = do+    let settleTx = getSettlementBitcoinTx recvChan recvSignFunc testAddrLivenet 0+    let clientChangeAmount = HT.outValue . head . HT.txOut $ settleTx+    -- Check that the client change amount in the settlement transaction equals the+    --  channel funding amount minus the sum of all payment amounts.+    let fundAmountMinusPaySum = S.pcsChannelTotalValue (getChannelState recvChan) -+            fromIntegral (sum amountSent)+    fromIntegral clientChangeAmount == fundAmountMinusPaySum++checkReceiverValue :: (ArbChannelPair, [BitcoinAmount]) -> Bool+checkReceiverValue (ArbChannelPair _ recvChan amountSent _ recvSignFunc, _) = do+    let settleTx = getSettlementBitcoinTx recvChan recvSignFunc testAddrLivenet 0+    let receiverAmount = HT.outValue (HT.txOut settleTx !! 1)+    -- Check receiver amount in settlement transaction with zero fee equals sum+    -- of all payments.+    (fromIntegral receiverAmount :: BitcoinAmount) == fromIntegral (sum amountSent)++checkSendRecvStateMatch :: (ArbChannelPair, [BitcoinAmount]) -> Bool+checkSendRecvStateMatch (ArbChannelPair sendChan recvChan _ _ _, _) =+    getChannelState sendChan == getChannelState recvChan++checkRecvSendAmount :: (ArbChannelPair, [BitcoinAmount]) -> Bool+checkRecvSendAmount (ArbChannelPair _ _ amountSent amountRecvd _, _) =+    amountSent == amountRecvd++testPaymentSession ::+    ((ArbChannelPair, [BitcoinAmount]) -> Bool)+    -> ArbChannelPair+    -> [BitcoinAmount]+    -> Bool+testPaymentSession testFunc arbChanPair paymentAmountList =+    testFunc (runChanPair arbChanPair paymentAmountList)++runChanPair :: ArbChannelPair -> [BitcoinAmount] -> (ArbChannelPair, [BitcoinAmount])+runChanPair chanPair paymentAmountList =+    (foldl doPayment chanPair paymentAmountList, paymentAmountList)++jsonSerDeser :: (Show a, Eq a, JSON.FromJSON a, JSON.ToJSON a) => a -> Bool+jsonSerDeser fp =+    maybe False checkEquals decodedObj+        where json = JSON.encode fp+              decodedObj = JSON.decode json+              checkEquals serDeserVal =+                if serDeserVal /= fp then+                        error ("Ser/deser mismatch.\nOriginal: " ++ show fp ++ "\nCopy: " ++ show decodedObj)+                    else+                        True++binSerDeser :: (Typeable a, Show a, Eq a, Bin.Serialize a) => a -> Bool+binSerDeser fp =+    checkEquals decodeRes+        where bs = Bin.encode fp+              decodeRes = deserEither bs+              checkEquals serDeserRes = case serDeserRes of+                    Left e      -> error $ "Serialize/deserialize error: " ++ show e+                    Right res   -> if res /= fp then+                                    error ("Ser/deser mismatch.\nOriginal: " ++ show fp ++ "\nCopy: " ++ show res)+                                else+                                    True++++ data ArbChannelPair = ArbChannelPair     SenderPaymentChannel ReceiverPaymentChannel [BitcoinAmount] [BitcoinAmount] (HC.Hash256 -> HC.Signature) @@ -39,7 +143,7 @@ doPayment (ArbChannelPair spc rpc sendList recvList f) amount =     let         (amountSent, pmn, newSpc) = sendPayment spc amount-        eitherRpc = recvPayment rpc pmn+        eitherRpc = recvPayment nowishTimestamp rpc pmn     in         case eitherRpc of             Left e -> error (show e)@@ -69,10 +173,10 @@     ArbitraryPubKey sendPriv sendPK <- arbitrary     -- receiver key pair     ArbitraryPubKey recvPriv recvPK <- arbitrary-    -- expiration date-    lockTime <- arbitrary+    -- TODO: We use an expiration date far off into the future for now+    let lockTime = parseBitcoinLocktime 2524651200     return (CChannelParameters-                (MkSendPubKey sendPK) (MkRecvPubKey recvPK) lockTime dUST_LIMIT,+                (MkSendPubKey sendPK) (MkRecvPubKey recvPK) lockTime,            (sendPriv, recvPriv))  mkChanPair :: Gen (ArbChannelPair, FullPayment)@@ -80,12 +184,11 @@         (cp, (sendPriv, recvPriv)) <- mkChanParams         fti <- arbitrary         -- value of first payment-        initPayAmount <- arbitrary -- fromIntegral <$> choose (0, chanAmount)+        initPayAmount <- arbitrary         -- create states-        let (initPayActualAmount,paymnt,sendChan) = channelWithInitialPaymentOf+        let (initPayActualAmount,paymnt,sendChan) = channelWithInitialPaymentOf defaultConfig                 cp fti (flip HC.signMsg sendPriv) (getFundingAddress cp) initPayAmount-        let eitherRecvChan = channelFromInitialPayment-                cp fti (getFundingAddress cp) paymnt+        let eitherRecvChan = channelFromInitialPayment nowishTimestamp defaultConfig cp fti paymnt         case eitherRecvChan of             Left e -> error (show e)             Right (initRecvAmount,recvChan) -> return@@ -94,9 +197,6 @@                         (flip HC.signMsg recvPriv),                     paymnt) -instance Arbitrary BitcoinLockTime where-    arbitrary = fmap parseBitcoinLocktime arbitrary- instance Arbitrary FundingTxInfo where     arbitrary = do         ArbitraryTxHash h <- arbitrary@@ -108,72 +208,3 @@  instance Arbitrary BitcoinAmount where     arbitrary = fromIntegral <$> choose (0, round $ 21e6 * 1e8 :: Integer)--runChanPair :: ArbChannelPair -> [BitcoinAmount] -> (ArbChannelPair, [BitcoinAmount])-runChanPair chanPair paymentAmountList =-    (foldl doPayment chanPair paymentAmountList, paymentAmountList)--checkChanPair :: (ArbChannelPair, [BitcoinAmount]) -> Bool-checkChanPair (ArbChannelPair sendChan recvChan amountSent amountRecvd recvSignFunc, _) = do-    let settleTx = getSettlementBitcoinTx recvChan recvSignFunc testAddrLivenet 0-    let clientChangeAmount = HT.outValue . head . HT.txOut $ settleTx-    -- Check that the client change amount in the settlement transaction equals the-    --  channel funding amount minus the sum of all payment amounts.-    let fundAmountMinusPaySum = S.pcsChannelTotalValue (getChannelState recvChan) --            fromIntegral (sum amountSent)-    let clientValueIsGood = fromIntegral clientChangeAmount == fundAmountMinusPaySum :: Bool-    -- Debug print-    let checkGoodClientValue goodCV = if not goodCV then-                error $ "Bad client change value. Actual: " ++ show clientChangeAmount ++-                        " should be " ++ show fundAmountMinusPaySum-            else-                goodCV-    -- Check that sender/receiver (client/server) agree on channel state-    let statesMatch = getChannelState recvChan == getChannelState sendChan-    -- Send/recv value lists match-    let recvSendAmountMatch = amountSent == amountRecvd-    checkGoodClientValue clientValueIsGood && statesMatch && recvSendAmountMatch----jsonSerDeser :: (Show a, Eq a, JSON.FromJSON a, JSON.ToJSON a) => a -> Bool-jsonSerDeser fp =-    maybe False checkEquals decodedObj-        where json = JSON.encode fp-              decodedObj = JSON.decode json-              checkEquals serDeserVal =-                if serDeserVal /= fp then-                        error ("Ser/deser mismatch.\nOriginal: " ++ show fp ++ "\nCopy: " ++ show decodedObj)-                    else-                        True--binSerDeser :: (Typeable a, Show a, Eq a, Bin.Serialize a) => a -> Bool-binSerDeser fp =-    checkEquals decodeRes-        where bs = Bin.encode fp-              decodeRes = deserEither bs-              checkEquals serDeserRes = case serDeserRes of-                    Left e      -> error $ "Serialize/deserialize error: " ++ show e-                    Right res   -> if res /= fp then-                                    error ("Ser/deser mismatch.\nOriginal: " ++ show fp ++ "\nCopy: " ++ show res)-                                else-                                    True--testPaymentSession :: ArbChannelPair -> [BitcoinAmount] -> Bool-testPaymentSession arbChanPair paymentAmountList =-    checkChanPair (runChanPair arbChanPair paymentAmountList)--testPaymentJSON = jsonSerDeser :: FullPayment -> Bool-testPaymentBin = binSerDeser :: FullPayment -> Bool-testScriptBin = binSerDeser :: ChanScript -> Bool--main :: IO ()-main = do-    quickCheckWith stdArgs testPaymentSession-    quickCheckWith stdArgs testPaymentJSON-    quickCheckWith stdArgs testPaymentBin---     quickCheckWith stdArgs testScriptBin----