haskell-opentimestamps-client (empty) → 0.4.1.0
raw patch · 9 files changed
+833/−0 lines, 9 filesdep +basedep +base16dep +bytestring
Dependencies added: base, base16, bytestring, containers, cryptonite, directory, filepath, haskell-opentimestamps, haskell-opentimestamps-client, memory, optparse-applicative, temporary, text, time
Files
- CHANGELOG.md +17/−0
- LICENSE +217/−0
- Makefile +70/−0
- README.md +30/−0
- app/Main.hs +149/−0
- cabal.project +21/−0
- haskell-opentimestamps-client.cabal +66/−0
- package.yaml +63/−0
- src/OpenTimestampsClient.hs +200/−0
+ CHANGELOG.md view
@@ -0,0 +1,17 @@+0.1.0.0, released 2025-09-04+ Initial version (work in progress)++0.2.0.0, released 2025-09-12+ Removed umbrella project (this library is now standalone)++0.2.1.0, released 2025-09-12+ Added release.sh (required for release build)++0.3.0.0, released 2025-09-12+ Updated version in package.yaml++0.4.0.0, released 2025-09-17+ Updated version reminder++0.4.1.0, released 2025-12-02+ Publish to Hackage
+ LICENSE view
@@ -0,0 +1,217 @@+Copyright A.H.M. (Mari) Donkers 2025.++Licensed under either of:++* BSD-3-Clause license (https://opensource.org/licenses/BSD-3-Clause)+* Apache License, version 2.0 (https://opensource.org/licenses/Apache-2.0)++As a user, you may use this code under either license, at your option.+++---------------------------------------------------------------------+BSD 3-Clause License+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Neil Mitchell nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+++---------------------------------------------------------------------+Apache License+Version 2.0, January 2004+<http://www.apache.org/licenses/>++Terms and Conditions for use, reproduction, and distribution+1. Definitions+"License" shall mean the terms and conditions for use, reproduction,+and distribution as defined by Sections 1 through 9 of this document.++"Licensor" shall mean the copyright owner or entity authorized by the+copyright owner that is granting the License.++"Legal Entity" shall mean the union of the acting entity and all+other entities that control, are controlled by, or are under common+control with that entity. For the purposes of this definition,+"control" means (i) the power, direct or indirect, to cause the+direction or management of such entity, whether by contract or+otherwise, or (ii) ownership of fifty percent (50%) or more of the+outstanding shares, or (iii) beneficial ownership of such entity.++"You" (or "Your") shall mean an individual or Legal Entity exercising+permissions granted by this License.++"Source" form shall mean the preferred form for making modifications,+including but not limited to software source code, documentation+source, and configuration files.++"Object" form shall mean any form resulting from mechanical+transformation or translation of a Source form, including but not+limited to compiled object code, generated documentation, and+conversions to other media types.++"Work" shall mean the work of authorship, whether in Source or Object+form, made available under the License, as indicated by a copyright+notice that is included in or attached to the work).++"Derivative Works" shall mean any work, whether in Source or Object+form, that is based on (or derived from) the Work and for which the+editorial revisions, annotations, elaborations, or other+modifications represent, as a whole, an original work of authorship.+For the purposes of this License, Derivative Works shall not include+works that remain separable from, or merely link (or bind by name) to+the interfaces of, the Work and Derivative Works thereof.++"Contribution" shall mean any work of authorship, including the+original version of the Work and any modifications or additions to+that Work or Derivative Works thereof, that is intentionally+submitted to Licensor for inclusion in the Work by the copyright+owner or by an individual or Legal Entity authorized to submit on+behalf of the copyright owner. For the purposes of this definition,+"submitted" means any form of electronic, verbal, or written+communication sent to the Licensor or its representatives, including+but not limited to communication on electronic mailing lists, source+code control systems, and issue tracking systems that are managed by,+or on behalf of, the Licensor for the purpose of discussing and+improving the Work, but excluding communication that is conspicuously+marked or otherwise designated in writing by the copyright owner as+"Not a Contribution."++"Contributor" shall mean Licensor and any individual or Legal Entity+on behalf of whom a Contribution has been received by Licensor and+subsequently incorporated within the Work.++2. Grant of Copyright License+Subject to the terms and conditions of this License, each Contributor+hereby grants to You a perpetual, worldwide, non-exclusive,+no-charge, royalty-free, irrevocable copyright license to reproduce,+prepare Derivative Works of, publicly display, publicly perform,+sublicense, and distribute the Work and such Derivative Works in+Source or Object form.++3. Grant of Patent License+Subject to the terms and conditions of this License, each Contributor+hereby grants to You a perpetual, worldwide, non-exclusive,+no-charge, royalty-free, irrevocable (except as stated in this+section) patent license to make, have made, use, offer to sell, sell,+import, and otherwise transfer the Work, where such license applies+only to those patent claims licensable by such Contributor that are+necessarily infringed by their Contribution(s) alone or by+combination of their Contribution(s) with the Work to which such+Contribution(s) was submitted. If You institute patent litigation+against any entity (including a cross-claim or counterclaim in a+lawsuit) alleging that the Work or a Contribution incorporated within+the Work constitutes direct or contributory patent infringement, then+any patent licenses granted to You under this License for that Work+shall terminate as of the date such litigation is filed.++4. Redistribution+You may reproduce and distribute copies of the Work or Derivative+Works thereof in any medium, with or without modifications, and in+Source or Object form, provided that You meet the following+conditions:++(a) You must give any other recipients of the Work or Derivative+Works a copy of this License; and+(b) You must cause any modified files to carry prominent notices+stating that You changed the files; and+(c) You must retain, in the Source form of any Derivative Works that+You distribute, all copyright, patent, trademark, and attribution+notices from the Source form of the Work, excluding those notices+that do not pertain to any part of the Derivative Works; and+(d) If the Work includes a "NOTICE" text file as part of its+distribution, then any Derivative Works that You distribute must+include a readable copy of the attribution notices contained within+such NOTICE file, excluding those notices that do not pertain to any+part of the Derivative Works, in at least one of the following+places: within a NOTICE text file distributed as part of the+Derivative Works; within the Source form or documentation, if+provided along with the Derivative Works; or, within a display+generated by the Derivative Works, if and wherever such third-party+notices normally appear. The contents of the NOTICE file are for+informational purposes only and do not modify the License. You may+add Your own attribution notices within Derivative Works that You+distribute, alongside or as an addendum to the NOTICE text from the+Work, provided that such additional attribution notices cannot be+construed as modifying the License.+You may add Your own copyright statement to Your modifications and+may provide additional or different license terms and conditions for+use, reproduction, or distribution of Your modifications, or for any+such Derivative Works as a whole, provided Your use, reproduction,+and distribution of the Work otherwise complies with the conditions+stated in this License.++5. Submission of Contributions+Unless You explicitly state otherwise, any Contribution intentionally+submitted for inclusion in the Work by You to the Licensor shall be+under the terms and conditions of this License, without any+additional terms or conditions. Notwithstanding the above, nothing+herein shall supersede or modify the terms of any separate license+agreement you may have executed with Licensor regarding such+Contributions.++6. Trademarks+This License does not grant permission to use the trade names,+trademarks, service marks, or product names of the Licensor, except+as required for reasonable and customary use in describing the origin+of the Work and reproducing the content of the NOTICE file.++7. Disclaimer of Warranty+Unless required by applicable law or agreed to in writing, Licensor+provides the Work (and each Contributor provides its Contributions)+on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,+either express or implied, including, without limitation, any+warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY,+or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for+determining the appropriateness of using or redistributing the Work+and assume any risks associated with Your exercise of permissions+under this License.++8. Limitation of Liability+In no event and under no legal theory, whether in tort (including+negligence), contract, or otherwise, unless required by applicable+law (such as deliberate and grossly negligent acts) or agreed to in+writing, shall any Contributor be liable to You for damages,+including any direct, indirect, special, incidental, or consequential+damages of any character arising as a result of this License or out+of the use or inability to use the Work (including but not limited to+damages for loss of goodwill, work stoppage, computer failure or+malfunction, or any and all other commercial damages or losses), even+if such Contributor has been advised of the possibility of such+damages.++9. Accepting Warranty or Additional Liability+While redistributing the Work or Derivative Works thereof, You may+choose to offer, and charge a fee for, acceptance of support,+warranty, indemnity, or other liability obligations and/or rights+consistent with this License. However, in accepting such obligations,+You may act only on Your own behalf and on Your sole responsibility,+not on behalf of any other Contributor, and only if You agree to+indemnify, defend, and hold each Contributor harmless for any+liability incurred by, or claims asserted against, such Contributor+by reason of your accepting any such warranty or additional+liability.
+ Makefile view
@@ -0,0 +1,70 @@+PROJECT=haskell-opentimestamps-client++.PHONY: help+help: ## print make targets+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'++package: package.yaml+ hpack++.PHONY: freeze+freeze: ## Freeze dependencies+ cabal freeze++.PHONY: clean+clean: ## Clean project+ cabal clean++.PHONY: build+build: package ## Build project+ cabal build++.PHONY: repl+repl: package ## Repl with project pre-loaded+ cabal repl $(PROJECT)++.PHONY: format+format: ## Format Haskell source code+ fourmolu -i `ag -l --haskell`++.PHONY: lint+lint: ## Lint Haskell source code+ hlint `ag -l --haskell`++.PHONY: ls+ls: ## get path of executable+ cabal ls-bin $(PROJECT)++.PHONY: run+run: package ## run the project with cabal+ cabal run++.PHONY: release+release: ## release build (no debug symbols, etc).+ cabal build --enable-executable-stripping all+ cabal list-bin "$(PROJECT)-exe"++.PHONY: check+check: ## runs cabal check+ cabal check++.PHONY: sdist+sdist: ## generate a distribution tarball+ cabal sdist++# HOOGLE="http://localhost:4041" emacs &+.PHONY: edit+edit: ## Start editor+ emacs &++# https://blog.ssanj.net/posts/2019-10-19-running-hoogle-locally-for-haskell-dev.html+.PHONY: hoogledb+hoogledb: ## Create local Hoogle database+ cabal-hoogle generate+ # stack hoogle -- generate --local++# https://blog.ssanj.net/posts/2019-10-19-running-hoogle-locally-for-haskell-dev.html+.PHONY: hoogle+hoogle: ## Run local Hoogle server+ cabal-hoogle run -- server --local --port 4041+ # stack hoogle -- server --local --port=4041
+ README.md view
@@ -0,0 +1,30 @@+This is a Haskell client for OpenTimestamps, providing a command-line utility to interact with the OpenTimestamps network. It's on Hackage here: [haskell-opentimestamps-client](https://hackage.haskell.org/package/haskell-opentimestamps-client). The client program allows users to:+* **Info:** Display information about a timestamp file.+* **Stamp:** Create OpenTimestamps for files.+* **Prune:** Prune a timestamp file.+* **Upgrade:** Upgrade remote calendar timestamps to be locally verifiable.+* **Verify:** Verify existing OpenTimestamps files.++The client uses the `haskell-opentimestamps` library for core OpenTimestamps stuff.++The project has a number of dependencies, including `cryptonite` for cryptographic operations, `http-client` for making requests to calendar servers and `haskoin-code` and `bitcoin-rpc` to make RPC-calls to a locally running 'blocksonly' pruned Bitcoin node, which is required for **Verification**.++Also you will need to have a `libsecp256k1` package (Debian: `libsecp256k1-dev`) installed on your Linux distribution (beware: it needs to be < 0.7.x because of deprecated functions in `0.7.x`).++To build an executable:++```sh+make release+```++Updating version (development reminder):+- `version: #.#.#.#` in `package.yaml`+- run `hpack` to regenerate the `.cabal` file+- Add the version to `CHANGELOG.md`+- Commit the new version.+- `git tag -a #.#.#.#` to add a git tag+- `git push yourorigin #.#.#.#` to push the git tag++**Work In Progress**...++There is also a Haskell library for working with OpenTimestamps. It's on Hackage here: [haskell-opentimestamps](https://hackage.haskell.org/package/haskell-opentimestamps).
+ app/Main.hs view
@@ -0,0 +1,149 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main (main) where++import qualified OpenTimestampsClient as OTC+import Options.Applicative+ ( Parser+ , command+ , execParser+ , fullDesc+ , help+ , helper+ , info+ , long+ , metavar+ , progDesc+ , short+ , some+ , strArgument+ , subparser+ , switch+ , (<**>)+ )++calendarUrls :: [String]+calendarUrls =+ [ "https://a.pool.opentimestamps.org"+ , "https://b.pool.opentimestamps.org"+ , "https://a.pool.eternitywall.com"+ , "https://ots.btc.catallaxy.com"+ ]++data GlobalOptions where+ GlobalOptions :: {globalVerbose :: Bool} -> GlobalOptions++verboseOption :: Parser Bool+verboseOption =+ switch+ ( long "verbose"+ <> short 'v'+ <> help "Enable verbose output"+ )++globalOptionsParser :: Parser GlobalOptions+globalOptionsParser =+ GlobalOptions+ <$> verboseOption++-- TODO Add GitExtract (?) to Extract timestamp for a single file from a timestamp git commit++data Command where+ Info :: FilePath -> Command+ Prune :: FilePath -> Command+ Stamp :: [FilePath] -> Command+ Upgrade :: FilePath -> Command+ Verify :: FilePath -> Command++infoParser :: Parser Command+infoParser =+ Info+ <$> strArgument+ ( metavar "FILE"+ <> help "Timestamp file (.ots)"+ )++pruneParser :: Parser Command+pruneParser =+ Prune+ <$> strArgument+ ( metavar "FILE"+ <> help "[TODO] Timestamp file (.ots)"+ )++stampParser :: Parser Command+stampParser =+ Stamp+ <$> some+ ( strArgument+ ( metavar "FILE"+ <> help "File(s) to timestamp"+ )+ )++upgradeParser :: Parser Command+upgradeParser =+ Upgrade+ <$> strArgument+ ( metavar "FILE"+ <> help "[TODO] Timestamp file (.ots)"+ )++verifyParser :: Parser Command+verifyParser =+ Verify+ <$> strArgument+ ( metavar "FILE"+ <> help "Timestamp file (.ots)"+ )++commandParser :: Parser Command+commandParser =+ subparser+ ( command+ "info"+ (info infoParser (progDesc "Show information on a timestamp"))+ <> command+ "prune"+ (info pruneParser (progDesc "Prune timestamp"))+ <> command+ "stamp"+ (info stampParser (progDesc "Timestamp file(s)"))+ <> command+ "upgrade"+ ( info+ upgradeParser+ (progDesc "Upgrade remote calendar timestamps to be locally verifiable")+ )+ <> command+ "verify"+ (info verifyParser (progDesc "Verify a timestamp"))+ )++data AppParser where+ AppParser :: GlobalOptions -> Command -> AppParser++appParser :: Parser AppParser+appParser = AppParser <$> globalOptionsParser <*> commandParser++-- | Main entry point of the application.+main :: IO ()+main = do+ AppParser gOpts cmd <-+ execParser (info (appParser <**> helper) (fullDesc <> progDesc "OpenTimestamps client"))+ run gOpts cmd+ where+ run :: GlobalOptions -> Command -> IO ()+ run _gOpts (Info path) = do+ -- putStrLn $ "Getting info for: " ++ path+ OTC.info path+ run gOpts (Prune path) = do+ OTC.prune (globalVerbose gOpts) path [""] [""] -- TODO verify- and discard args+ run gOpts (Stamp paths) = do+ OTC.stamp (globalVerbose gOpts) paths calendarUrls+ run gOpts (Upgrade path) = do+ OTC.upgrade (globalVerbose gOpts) path+ run gOpts (Verify path) = do+ OTC.verify (globalVerbose gOpts) path
+ cabal.project view
@@ -0,0 +1,21 @@+packages: .+ +source-repository-package+ type: git+ location: https://github.com/bitnomial/bitcoin-compact-filters+ -- branch: master+ tag: 7ae4d9bec8a5275db579e34e3cadb86c0f0461e9++source-repository-package+ type: git+ location: https://github.com/bitnomial/bitcoind-rpc+ -- branch: master+ tag: 16d664e05c232800aa6bc4f63c372c6f3e0e5d39+ subdir: bitcoind-rpc++source-repository-package+ type: git+ location: https://codeberg.org/photonsphere/haskell-opentimestamps.git+ --location: /absolutepathonyourcomputerhere/haskell-opentimestamps+ branch: master+ --tag: 0.1.0.0
+ haskell-opentimestamps-client.cabal view
@@ -0,0 +1,66 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.38.2.+--+-- see: https://github.com/sol/hpack++name: haskell-opentimestamps-client+version: 0.4.1.0+synopsis: Haskell implementation of a basic OpenTimestamps client+description: Please see the README on Codeberg at <https://codeberg.org/photonsphere/haskell-opentimestamps-client#readme>+category: Bitcoin, Finance, Network, Cryptography+author: A.H.M. (Mari) Donkers+maintainer: a.h.m.donkers@photonsphere.org+copyright: 2025 A.H.M. (Mari) Donkers+license: BSD3+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ CHANGELOG.md+ cabal.project+ package.yaml+ Makefile++source-repository head+ type: git+ location: https://codeberg.org/photonsphere/haskell-opentimestamps-client++library+ exposed-modules:+ OpenTimestampsClient+ other-modules:+ Paths_haskell_opentimestamps_client+ hs-source-dirs:+ src+ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints+ build-depends:+ base >=4.7 && <5+ , base16 >=0.1 && <2.0+ , bytestring >=0.10 && <0.13+ , containers >=0.5 && <0.8+ , cryptonite >=0.25 && <0.35+ , directory >=1.2 && <1.4+ , filepath >=1.3 && <1.5+ , haskell-opentimestamps >=0.5.5.0 && <1.0+ , memory >=0.14 && <0.19+ , temporary >=1.2 && <1.4+ , text >=1.2 && <2.2+ , time >=1.4 && <1.13+ default-language: Haskell2010++executable haskell-opentimestamps-client-exe+ main-is: Main.hs+ other-modules:+ Paths_haskell_opentimestamps_client+ hs-source-dirs:+ app+ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , bytestring >=0.10 && <0.13+ , cryptonite >=0.25 && <0.35+ , haskell-opentimestamps-client+ , memory >=0.14 && <0.19+ , optparse-applicative >=0.14 && <0.19+ default-language: Haskell2010
+ package.yaml view
@@ -0,0 +1,63 @@+name: haskell-opentimestamps-client+version: 0.4.1.0+license: BSD3+#x-license: BSD-3-Clause OR Apache-2.0+author: "A.H.M. (Mari) Donkers"+maintainer: "a.h.m.donkers@photonsphere.org"+copyright: "2025 A.H.M. (Mari) Donkers"+git: "https://codeberg.org/photonsphere/haskell-opentimestamps-client"+category: Bitcoin, Finance, Network, Cryptography+synopsis: Haskell implementation of a basic OpenTimestamps client++extra-source-files:+- README.md+- CHANGELOG.md+- cabal.project+- package.yaml+- Makefile++description: Please see the README on Codeberg at <https://codeberg.org/photonsphere/haskell-opentimestamps-client#readme>++ghc-options:+- -Wall+- -Wcompat+- -Widentities+- -Wincomplete-record-updates+- -Wincomplete-uni-patterns+- -Wmissing-export-lists+- -Wmissing-home-modules+- -Wpartial-fields+- -Wredundant-constraints++library:+ source-dirs: src+ dependencies:+ - base >= 4.7 && < 5+ - bytestring >= 0.10 && < 0.13+ - cryptonite >= 0.25 && < 0.35+ - directory >= 1.2 && < 1.4+ - filepath >= 1.3 && < 1.5+ - haskell-opentimestamps >= 0.5.5.0 && < 1.0+ - memory >= 0.14 && < 0.19+ - temporary >= 1.2 && < 1.4+ - text >= 1.2 && < 2.2+ - time >= 1.4 && < 1.13+ - base16 >= 0.1 && < 2.0+ - containers >= 0.5 && < 0.8++executables:+ haskell-opentimestamps-client-exe:+ main: Main.hs+ source-dirs: app+ ghc-options:+ - -threaded+ - -rtsopts+ - -with-rtsopts=-N+ dependencies:+ - base >= 4.7 && < 5+ - bytestring >= 0.10 && < 0.13+ - cryptonite >= 0.25 && < 0.35+ - haskell-opentimestamps-client+ - memory >= 0.14 && < 0.19+ - optparse-applicative >= 0.14 && < 0.19+
+ src/OpenTimestampsClient.hs view
@@ -0,0 +1,200 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}++module OpenTimestampsClient+ ( info+ , prune+ , stamp+ , upgrade+ , verify+ ) where++import Control.Monad (unless, when)+import Crypto.Hash (SHA256 (SHA256), hashWith)+import qualified Data.ByteArray as BA+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy.Char8 as BSL+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import Data.Time.Format (defaultTimeLocale, formatTime)+import Data.Time.LocalTime (TimeZone (..), utcToZonedTime)+import qualified OpenTimestamps as OT+import qualified OpenTimestamps.DetachedTimestampFile as DTSF+import OpenTimestamps.Timestamp (printHex, printTimestamp, timestampMsg)+import qualified OpenTimestamps.Verify as Verify+import System.Directory (doesFileExist, renameFile)+import System.FilePath (addExtension, stripExtension, takeDirectory)+import System.IO (hClose, hPutStrLn, stderr)+import System.IO.Temp (withTempFile)++data RPCCredentials where+ RPCCredentials ::+ {rpcUser :: T.Text, rpcPassword :: T.Text} ->+ RPCCredentials+ deriving (Show, Eq)++readCookieFile :: FilePath -> IO (Maybe RPCCredentials)+readCookieFile path = do+ exists <- doesFileExist path+ if exists+ then do+ content <- TIO.readFile path+ case T.splitOn ":" content of+ [user, pass] -> return $ Just $ RPCCredentials user pass+ _ -> return Nothing+ else do+ hPutStrLn stderr $ "Cookie file not found at: " ++ path+ return Nothing++info :: FilePath -> IO ()+info path =+ processStream =<< BSL.readFile path+ where+ processStream otsFileContent = case DTSF.deserialize otsFileContent of+ Left err ->+ putStrLn $ "Error deserializing: " ++ show err+ Right dtfs -> do+ putStrLn $+ "File sha256 hash: "+ ++ printHex+ ( timestampMsg+ (DTSF.timestamp dtfs)+ )+ putStrLn "Timestamp:"+ putStrLn $ printTimestamp 0 (DTSF.timestamp dtfs)++prune :: Bool -> FilePath -> [String] -> [String] -> IO ()+prune verbose path verifyArgs discardArgs = do+ when verbose $ putStrLn $ "Reading timestamp file: " ++ path+ otsFileContent <- BSL.readFile path+ when verbose $ putStrLn $ "Pruning timestamp: " ++ path+ result <- OT.prune verifyArgs discardArgs otsFileContent+ case result of+ Left err -> do+ hPutStrLn stderr $ "Error pruning timestamp: " ++ err+ Right newOtsContent -> do+ let dir = takeDirectory path+ let tmpFileName = path ++ ".tmp"+ tmpExists <- doesFileExist tmpFileName+ when tmpExists $+ fail $+ "Intermediate temporary file " ++ tmpFileName ++ " already exists"+ let bakFileName = path ++ ".bak"+ bakExists <- doesFileExist bakFileName+ when bakExists $+ fail $+ "Backup file " ++ bakFileName ++ " already exists"++ withTempFile+ dir+ tmpFileName+ ( \tempPath tempHandle -> do+ BSL.hPutStr tempHandle newOtsContent+ hClose tempHandle+ renameFile path bakFileName+ renameFile tempPath path+ putStrLn $ "Timestamp pruned and saved to " ++ path+ )++stamp :: Bool -> [FilePath] -> [String] -> IO ()+stamp verbose paths calendarUrls = do+ mapM_ stampOne paths+ where+ stampOne path = do+ let newOtsPath = addExtension path ".ots"+ exists <- doesFileExist newOtsPath+ when exists $+ fail $+ "Failed to create timestamp '" ++ newOtsPath ++ "' (file already exists)."+ when verbose $ putStrLn $ "Reading content of: " ++ path+ content <- BS.readFile path+ -- TODO Use OpenTimestamps library function (t.b.d.) with type.+ let digest = BA.convert (hashWith SHA256 content)+ when verbose $ putStrLn $ "Stamping: " ++ path+ sr <- OT.stamp calendarUrls digest+ -- TODO Nicer error formatting.+ let errors = OT.srErrors sr+ if verbose+ then mapM_ print errors+ else+ unless (null errors) $+ hPutStrLn stderr "There were calender servers with errors (use global option -v for details)"+ case OT.srTimestampFile sr of+ Nothing -> hPutStrLn stderr "No timestamps could be retrieved"+ Just dtsf -> do+ let dtfsNew = OT.serialize dtsf+ BSL.writeFile newOtsPath dtfsNew+ putStrLn $ "Timestamp saved to " ++ newOtsPath++upgrade :: Bool -> FilePath -> IO ()+upgrade verbose path = do+ when verbose $ putStrLn $ "Reading timestamp file: " ++ path+ otsFileContent <- BSL.readFile path+ when verbose $ putStrLn $ "Upgrading timestamp: " ++ path+ -- TODO The `[]` is for calendarUrls (what to do with them?)++ result <- OT.upgrade [] otsFileContent+ case result of+ Left err -> do+ hPutStrLn stderr $ "Error upgrading timestamp: " ++ err+ Right newOtsContent -> do+ let dir = takeDirectory path+ let tmpFileName = path ++ ".tmp"+ tmpExists <- doesFileExist tmpFileName+ when tmpExists $ fail $ "Intermediate temporary file " ++ tmpFileName ++ " already exists"+ let bakFileName = path ++ ".bak"+ bakExists <- doesFileExist bakFileName+ when bakExists $ fail $ "Backup file " ++ bakFileName ++ " already exists"++ withTempFile+ dir+ tmpFileName+ ( \tempPath tempHandle -> do+ BSL.hPutStr tempHandle newOtsContent+ hClose tempHandle+ renameFile path bakFileName+ renameFile tempPath path+ putStrLn $ "Timestamp upgraded and saved to " ++ path+ )++verify :: Bool -> FilePath -> IO ()+verify verbose path = do+ let cookieFilePath = "/home/mdo/.bitcoin/.cookie"+ mrpcc <- readCookieFile cookieFilePath+ let bitcoinConfig = case mrpcc of+ Nothing ->+ Verify.BitcoinConfig+ { Verify.bitcoinHost = "localhost"+ , Verify.bitcoinPort = 8332+ , Verify.bitcoinUser = "bitcoin"+ , Verify.bitcoinPass = "bitcoinpass"+ }+ Just rpcc ->+ Verify.BitcoinConfig+ { Verify.bitcoinHost = "localhost"+ , Verify.bitcoinPort = 8332+ , Verify.bitcoinUser = T.unpack $ rpcUser rpcc+ , Verify.bitcoinPass = T.unpack $ rpcPassword rpcc+ }++ print $ "Cookiefile path: " ++ cookieFilePath+ -- print $ "User: " ++ Verify.bitcoinUser bitcoinConfig+ -- print $ "Password: " ++ Verify.bitcoinPass bitcoinConfig+ case stripExtension ".ots" path of+ Nothing -> hPutStrLn stderr $ "Not a timestamp? " ++ path+ Just srcPath -> do+ content <- BS.readFile srcPath+ let digest = BA.convert (hashWith SHA256 content)+ when verbose $ putStrLn $ "Reading timestamp file: " ++ path+ otsFileContent <- BSL.readFile path+ when verbose $ putStrLn $ "Verifying timestamp: " ++ path+ -- TODO The `[]` is for calendarUrls (what to do with them?)++ result <- OT.verify bitcoinConfig [] otsFileContent digest+ case result of+ Right (blockHeight, utcTime) -> do+ let zonedTime = utcToZonedTime (TimeZone (-(1 * 60 * 4)) False "CEST") utcTime -- TODO Assuming CEST is UTC+2, so -2 hours from UTC+ let formattedTime = formatTime defaultTimeLocale "%Y-%m-%d %H:%M:%S %Z" zonedTime+ putStrLn $+ "Success! Bitcoin block " ++ show blockHeight ++ " attests existence as of " ++ formattedTime+ Left err -> putStrLn $ "Failed " ++ show err