├── .gitignore ├── README.md ├── project.clj ├── src └── bitcoin │ ├── channels.clj │ ├── core.clj │ └── deterministic.clj └── test └── bitcoin └── test ├── core.clj └── deterministic.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /pom.xml 2 | *jar 3 | /lib 4 | /classes 5 | /native 6 | /.lein-failures 7 | /checkouts 8 | /.lein-deps-sum 9 | /test.wallet 10 | 11 | *.db 12 | 13 | *.blockchain 14 | 15 | target/ 16 | *.bup 17 | 18 | *.wallet1 19 | 20 | .lein-repl-history 21 | *.privkey 22 | *.wallet 23 | 24 | .nrepl-port 25 | 26 | *.spv-blockchain 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bitcljoin 2 | 3 | Start of a Clojure wrapper for BitCoinJ http://www.bitcoinj.org/ 4 | 5 | This is absolutely a work in progress. Including documentation (and my understanding of bitcoinj). The API is under a lot of flux. Hope to stabilize it somewhat by 1.0. 6 | 7 | If you're interested please email me. 8 | 9 | ## Usage 10 | 11 | Add the following to your project.clj 12 | 13 | ```clojure 14 | [bitcljoin "0.4.5"] 15 | ``` 16 | 17 | Use library: 18 | 19 | ```clojure 20 | (use 'bitcoin.core) 21 | ``` 22 | 23 | Create a keypair: 24 | 25 | ```clojure 26 | (create-keypair) 27 | ``` 28 | 29 | Create an address from a keypair: 30 | 31 | ```clojure 32 | (->address (create-keypair)) 33 | ``` 34 | 35 | Create an address from a String: 36 | 37 | ```clojure 38 | (->address "17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL") 39 | ``` 40 | 41 | Load a private key using the BitCoin privkey dumpformat: 42 | 43 | ```clojure 44 | (->kp "5KQ3SHTDHfU6XhBp7sSCbUoMpiZQKfKc7jVjAb6rHiegq1m2VWq") 45 | ``` 46 | 47 | Export a keypair to BitCoin privkey dump format: 48 | 49 | ```clojure 50 | (->private kp) 51 | ``` 52 | 53 | Create or load a wallet from a file. If creating it creates a keypair by default: 54 | 55 | ```clojure 56 | (open-wallet "./mywallet.wallet") 57 | ``` 58 | 59 | Get the keychain of a wallet: 60 | 61 | ```clojure 62 | (keychain (wallet "./mywallet.wallet")) 63 | ``` 64 | 65 | Create an in memory wallet for a single keypair: 66 | 67 | ```clojure 68 | (kp->wallet kp) 69 | ``` 70 | 71 | Send coins: 72 | 73 | ```clojure 74 | (send-coins wallet "16mJ5mGvj3xdmQhspPxFLp8ScYjirDoKxN" 1000) ;; Use nano coins TODO use BigDec 75 | ``` 76 | 77 | Register when a payment is received: 78 | 79 | ```clojure 80 | (on-coins-received wallet (fn [tx prev-balance new-balance] (prn tx))) 81 | ``` 82 | 83 | Download block-chain and query it: 84 | 85 | ```clojure 86 | (start) 87 | (download-block-chain) 88 | 89 | ;; a sequence of blocks starting with the current head and going backwards 90 | (take 10 (stored-blocks)) 91 | ``` 92 | 93 | ## Full or regular block chain 94 | 95 | Traditionally BitcoinJ downloads a simpler quicker version of the Block Chain and only stores transactions related to your wallet. If you're using this to create and manage payments stick with the regular block chain as shown above. 96 | 97 | If you're interested in bitcoin as a whole say for analytics. BitcoinJ now supports a new more complete blockchain download. 98 | 99 | You can start this using start full which also returns the block chain. 100 | 101 | ```clojure 102 | (start-full) 103 | ``` 104 | 105 | ## Channels 106 | 107 | [Lamina](https://github.com/ztellman/lamina) is a great new library for managing asyncronous communications. One of the key abstractions 108 | is the [Channel](https://github.com/ztellman/lamina/wiki/Channels-new) which can be thought of as a pub/sub system that looks a bit like a sequence. 109 | 110 | This is all experimental but I think this is a much better way of doing analytics on the BitCoin economy. 111 | 112 | If you were using this pre 0.3 please note that this has changed a bit. In particular we have a new namespace bitcoin.channels. Channels are also not created automatically. 113 | 114 | 115 | ```clojure 116 | (require '[bitcoin.core :as btc]) 117 | (use 'bitcoin.channels) 118 | (require '[lamina.core :as l]) 119 | 120 | (btc/start-full) ; Starts the full block chain downloader which gives a more complete view of the bitcoin economy 121 | 122 | (def txs (broadcast-txs)) ;; A channel of all new transactions. Returns Transaction objects straight from BitcoinJ 123 | (def clj-txs (txs->maps txs)) ;; A Channel of all transactions but returned as clojure maps 124 | (def dice (txs-for "1dice8EMZmqKvrGE4Qc9bUFf9PX3xaYDp")) 125 | (def new-blocks (blocks)) 126 | 127 | 128 | (lamina.core/take* 1 txs) ;; get the first transaction off the channel 129 | (lamina.core/take* 1 new-blocks) ;; get the first block off the channel 130 | 131 | ; Lamina allows us to create a new channel using map* which works exactly like clojure's regular map except on channels 132 | (def block-time (lamina.core/map* #(.getTime %) blocks)) ; Create a new channel containing all the timestamps of the blocks 133 | 134 | ;; It is good practice to combine filter* and map* to process the data in the way you want 135 | 136 | ;; e.g. to find all the addresses who receive the coinbase 137 | (->> (broadcast-txs) 138 | (lamina.core/filter* btc/coin-base?) ;; Filter channel to only contain coinbase transactions 139 | (lamina.core/map* #(btc/output->address (first (.getOutputs %))))) ;; Find the address of the first output 140 | ``` 141 | 142 | ## Deterministic Keys 143 | 144 | As of 0.4 we now have a clojure wrapper around BitcoinJ's Deterministic Key support. Deterministic Keys are based on [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) and allows you to recreate a key hierarchy based on a single secret and public key pair. 145 | 146 | ```clojure 147 | (use 'bitcoin.deterministic) 148 | 149 | (def mk (create-master-key)) ;; Creates the master key pair. 150 | 151 | ;; To recreate the master key with the private key (For spending purposes) you need to save the private key bytes as well as the chain code. 152 | 153 | (def priv (->priv-bytes mk)) ;; Save this securely 154 | (def cc (->chain-code mk)) ;; Save this. It is required to create both private and public keys 155 | 156 | (recreate-master-key priv cc) ;; Recreate the actual master key 157 | 158 | ;; You can create a public key version of the master key. This allows you to create addresses in the hierarchy but not spend outputs for it. 159 | 160 | (def pub (->pub-bytes mk)) 161 | 162 | 163 | (recreate-master-pub-key pub cc) ;; Recreate the actual master key but only the public key 164 | 165 | 166 | ;; Derive a key 167 | 168 | (def child (derive-key mk 2)) 169 | 170 | ;; Show the path of the hierarchy 171 | (->path child) 172 | # "M/2" 173 | 174 | ;; Add a grand child 175 | (def grand-child (derive-key child 1)) 176 | 177 | ;; Show the path of the hierarchy 178 | (->path grand-child) 179 | # "M/2/1" 180 | 181 | ;; Find the key for a particular path 182 | (derive-from-path mk "M/1/3") 183 | 184 | ;; Return the EC Keypair for use in signing of a Deterministic key 185 | (dk->kp child) 186 | 187 | ;; Return bitcoin address for deterministic key 188 | 189 | (require '[bitcoin.core :as btc]) 190 | 191 | (btc/->address child) 192 | 193 | ``` 194 | 195 | 196 | ## TODO: 197 | 198 | * Transactions 199 | * Create transactions 200 | 201 | 202 | ## License 203 | 204 | Copyright (C) 2012 Pelle Braendgaard http://stakeventures.com 205 | 206 | Distributed under the Eclipse Public License, the same as Clojure. 207 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject bitcljoin "0.4.5" 2 | :description "BitCoin library for Clojure" 3 | :dependencies [ 4 | [org.clojure/clojure "1.5.1"] 5 | [com.google/bitcoinj "0.11.2"] 6 | [com.h2database/h2 "1.3.170"] 7 | [lamina "0.5.0"] 8 | [bux "0.2.1"]] 9 | ;; :jvm-opts ["-Xmx1g"] 10 | :profiles { :dev { :dependencies [[postgresql "9.1-901.jdbc4"] 11 | [ch.qos.logback/logback-classic "1.0.7"]]}} 12 | ) 13 | -------------------------------------------------------------------------------- /src/bitcoin/channels.clj: -------------------------------------------------------------------------------- 1 | (ns bitcoin.channels 2 | (:use clojure.stacktrace) 3 | (:require [lamina.core :as lamina] 4 | [bitcoin.core :as btc])) 5 | 6 | (defn txs->maps 7 | [ch] 8 | (lamina/filter* btc/tx->map ch)) 9 | 10 | (defn blocks->maps 11 | [ch] 12 | (lamina/filter* btc/block->map ch)) 13 | 14 | (defn broadcast-txs 15 | "Returns a of new transactions being broadcast (not yet in the blockchain)" 16 | ([pg] 17 | (let [ch (lamina/permanent-channel)] 18 | (btc/on-tx-broadcast pg (fn [_ tx] (lamina/enqueue ch tx))) 19 | ch)) 20 | ([] (broadcast-txs @btc/current-pg))) 21 | 22 | (defn txs-for 23 | "Creates a channel for transactions sent to a specific address 24 | 25 | In it's single parameter form it sets up a broadcast-tx-channel of unconfirmed transactions. 26 | 27 | for confirmed transactions pass the channel in as the first argument" 28 | 29 | ([channel address] 30 | (lamina/filter* #((btc/to-addresses %) address) channel)) 31 | ([address] 32 | (txs-for (broadcast-txs) address))) 33 | 34 | (defn wallet-txs 35 | "Creates a channel for transactions sent to a specific address 36 | 37 | In it's single parameter form it sets up a broadcast-tx-channel of unconfirmed transactions. 38 | 39 | for confirmed transactions pass the channel in as the first argument" 40 | 41 | ([channel wallet] 42 | (lamina/filter* #(btc/for-me? % wallet) channel)) 43 | ([wallet] 44 | (wallet-txs (broadcast-txs) wallet))) 45 | 46 | 47 | (defn confirmed-txs 48 | "Returns a of new transactions entering the blockchain" 49 | ([bc] 50 | (let [ch (lamina/permanent-channel)] 51 | (.addListener bc (proxy 52 | [com.google.bitcoin.core.AbstractBlockChainListener][] 53 | (isTransactionRelevant [tx] true) 54 | (receiveFromBlock [tx _ _ _] (lamina/enqueue ch tx)) 55 | (notifyNewBestBlock [block] nil))) 56 | ch)) 57 | ([] (confirmed-txs @btc/current-bc))) 58 | 59 | (defn blocks 60 | "Returns a of new blocks entering the blockchain" 61 | ([bc] 62 | (let [ch (lamina/permanent-channel)] 63 | (.addListener bc (proxy 64 | [com.google.bitcoin.core.AbstractBlockChainListener][] 65 | (isTransactionRelevant [tx] true) 66 | (receiveFromBlock [tx _ _ _] nil) 67 | (notifyNewBestBlock [block] (lamina/enqueue ch block)))) 68 | ch)) 69 | ([] (blocks @btc/current-bc))) 70 | 71 | (defn txs->maps 72 | [ch] 73 | (lamina/map* btc/tx->map ch)) 74 | 75 | (defn blocks->maps 76 | [ch] 77 | (lamina/map* btc/block->map ch)) 78 | 79 | (defn log-errors 80 | "Log all errors" 81 | [ch] 82 | (lamina/on-error ch #(print-cause-trace %))) 83 | 84 | (defn prn-all 85 | "prn content of channel" 86 | [ch] 87 | (lamina/receive-all ch prn)) 88 | -------------------------------------------------------------------------------- /src/bitcoin/core.clj: -------------------------------------------------------------------------------- 1 | (ns bitcoin.core 2 | (:use [clojure.java.io :only [as-file]] 3 | [clojure.set :only [intersection]] 4 | [bux.currencies :only [BTC]])) 5 | 6 | 7 | (defn prodNet [] (com.google.bitcoin.core.NetworkParameters/prodNet)) 8 | (defn testNet [] (com.google.bitcoin.core.NetworkParameters/testNet)) 9 | (def ^:dynamic network) 10 | 11 | (defn net [] 12 | (if (bound? (var network)) 13 | network 14 | (do 15 | (def network (prodNet)) 16 | network))) 17 | 18 | (defn use-test-net [] 19 | (def network (testNet))) 20 | 21 | (def current-bc (atom nil)) 22 | (def current-pg (atom nil)) 23 | 24 | (defn create-keypair [] 25 | (com.google.bitcoin.core.ECKey. )) 26 | 27 | (defn ->private 28 | "encodes private keys in the form used by the Bitcoin dumpprivkey command" 29 | [kp] 30 | (str (.getPrivateKeyEncoded kp network))) 31 | 32 | (defn ->kp 33 | "decodes private keys in the form used by the Bitcoin dumpprivkey command" 34 | [s] 35 | (.getKey (com.google.bitcoin.core.DumpedPrivateKey. network s))) 36 | 37 | (defn keychain [w] 38 | (.getKeys w)) 39 | 40 | 41 | (defn create-wallet 42 | ([] (create-wallet (net))) 43 | ([network] (com.google.bitcoin.core.Wallet. network))) 44 | 45 | (defn add-keypair [wallet kp] 46 | (.addKey wallet kp) 47 | wallet) 48 | 49 | (defn open-wallet [filename] 50 | (let [file (as-file filename)] 51 | (try 52 | (com.google.bitcoin.core.Wallet/loadFromFile file) 53 | (catch com.google.bitcoin.store.UnreadableWalletException e 54 | (let 55 | [w (create-wallet) 56 | kp (create-keypair)] 57 | (add-keypair w kp) 58 | (.saveToFile w file) 59 | w))))) 60 | 61 | (defn register-wallet 62 | "Register the wallet with the blockchain and peergroup" 63 | ([wallet] (register-wallet wallet @current-bc @current-pg)) 64 | ([wallet bc pg] 65 | (.addWallet bc wallet) 66 | (.addWallet pg wallet) 67 | wallet)) 68 | 69 | (defn kp->wallet 70 | "Create and register a wallet for the keypair" 71 | ([kp] 72 | (-> (create-wallet) 73 | (add-keypair kp) 74 | (register-wallet)))) 75 | 76 | (defn ->BTC 77 | "Convert nanocoins to BTC" 78 | [nano] 79 | (BTC (/ nano 100000000))) 80 | 81 | (defprotocol Addressable 82 | (->address [k] [k network])) 83 | 84 | (extend-type com.google.bitcoin.core.ECKey Addressable 85 | (->address 86 | ([keypair] (->address keypair (net))) 87 | ([keypair network] (.toAddress keypair network)))) 88 | 89 | (extend-type (class (byte-array nil)) Addressable 90 | (->address 91 | ([pub] (->address pub (net))) 92 | ([pub network] (new com.google.bitcoin.core.Address network (com.google.bitcoin.core.Utils/sha256hash160 pub))))) 93 | 94 | (extend-type com.google.bitcoin.core.Address Addressable 95 | (->address 96 | ([address] address))) 97 | 98 | (extend-type String Addressable 99 | (->address 100 | ([keypair] (->address keypair (net))) 101 | ([keypair network] (new com.google.bitcoin.core.Address network keypair)))) 102 | 103 | (defn memory-block-store 104 | ([] (memory-block-store (net))) 105 | ([network] (com.google.bitcoin.store.MemoryBlockStore. network))) 106 | 107 | (defn file-block-store 108 | ([] (file-block-store "bitcljoin")) 109 | ([name] (file-block-store (net) name)) 110 | ([network name] (com.google.bitcoin.store.SPVBlockStore. network (java.io.File. (str name ".spv-blockchain"))))) 111 | 112 | (defn h2-full-block-store 113 | ([] (h2-full-block-store "bitcljoin")) 114 | ([name] (h2-full-block-store (net) name)) 115 | ([network name] (com.google.bitcoin.store.H2FullPrunedBlockStore. network name 300000))) 116 | 117 | (defn pg-full-block-store 118 | ([] (pg-full-block-store nil "bitcljoin" "" "")) 119 | ([host-name db-name user-name password] (pg-full-block-store (net) host-name db-name user-name password 300000)) 120 | ([network host-name db-name user-name password depth] (com.google.bitcoin.store.PostgresFullPrunedBlockStore. network depth host-name db-name user-name password))) 121 | 122 | 123 | (defn block-chain 124 | ([] (block-chain (file-block-store))) 125 | ([block-store] (com.google.bitcoin.core.BlockChain. (net) block-store))) 126 | 127 | (defn full-block-chain 128 | ([] (full-block-chain (h2-full-block-store))) 129 | ([block-store] (com.google.bitcoin.core.FullPrunedBlockChain. (net) block-store))) 130 | 131 | (defn peer-group 132 | ([] (peer-group (net) @current-bc)) 133 | ([chain] (peer-group (net) chain)) 134 | ([network chain] 135 | (let [ group (com.google.bitcoin.core.PeerGroup. network chain)] 136 | (.setUserAgent group "BitCljoin" "0.1.0") 137 | (.addPeerDiscovery group (com.google.bitcoin.net.discovery.SeedPeers. (net))) 138 | group))) 139 | 140 | (defn init 141 | "setup a block chain and peer-group using default settings. This should work well for e-commerce and general purpose payments" 142 | [] 143 | (reset! current-bc (block-chain)) 144 | (reset! current-pg (peer-group @current-bc))) 145 | 146 | (defn init-full 147 | "setup a full block chain and peer-group using default settings. Use this if you're more interested in analyzing the block chain." 148 | [] 149 | (reset! current-bc (full-block-chain)) 150 | (reset! current-pg (peer-group @current-bc))) 151 | 152 | (defn bc->store 153 | "returns the block store used by a block chain" 154 | [bc] 155 | (.getBlockStore bc)) 156 | 157 | (defn sha256hash 158 | "Note this doesn't perform a hash. It just wraps a string within the Sha256Hash class" 159 | [hash-string] 160 | (com.google.bitcoin.core.Sha256Hash. hash-string)) 161 | 162 | (defn genesis-hash [] 163 | (sha256hash "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")) 164 | 165 | (defn header 166 | "Actual block header from a stored block" 167 | [stored-block] 168 | (.getHeader stored-block)) 169 | 170 | (defn stored-blocks 171 | "Lazy sequence of stored blocks from the head and back" 172 | ([] (stored-blocks (bc->store @current-bc))) 173 | ([bs] (stored-blocks bs (.getChainHead bs))) 174 | ([bs sb] (if sb (cons sb (lazy-seq (stored-blocks bs (.getPrev sb bs))))))) 175 | 176 | (defn block-headers 177 | "Lazy sequence of block headers from the head and back" 178 | [bs] (map header (stored-blocks bs))) 179 | 180 | (defn coin-base? [tx] 181 | (.isCoinBase tx)) 182 | 183 | (defn tx-inputs [tx] 184 | (.getInputs tx)) 185 | 186 | (defn sender 187 | "Returns address of first input of transaction" 188 | [tx] 189 | (.getFromAddress (first (tx-inputs tx)))) 190 | 191 | (defn balance 192 | "returns the balance of the wallet" 193 | [wallet] 194 | (.getBalance wallet)) 195 | 196 | (defn amount-received [tx w] 197 | (.getValueSentToMe tx w)) 198 | 199 | (defn regular-inputs 200 | "non coinbase inputs" 201 | [tx] 202 | (filter #(not (coin-base? %)) (tx-inputs tx))) 203 | 204 | (defn input->address [i] 205 | (try 206 | (.getFromAddress i) 207 | (catch com.google.bitcoin.core.ScriptException e nil))) 208 | 209 | (defn input-addresses 210 | "Get the from addresses for a transaction" 211 | [tx] 212 | (remove nil? (map input->address (regular-inputs tx)))) 213 | 214 | (defn tx-outputs [tx] 215 | (.getOutputs tx)) 216 | 217 | ;; This is a essentially a duplicate of input-addresses can't remember why they are both there 218 | (defn from-addresses 219 | "Get the from addresses for a transaction" 220 | [tx] 221 | (input-addresses tx)) 222 | 223 | (defn sig->address [sig] 224 | "Returns the address string for an outputs script pubkey" 225 | (try 226 | (if (and sig (.isSentToAddress sig)) 227 | (str (.getToAddress sig (net)))) 228 | (catch com.google.bitcoin.core.ScriptException e nil))) 229 | 230 | (defn output->address [o] 231 | "Returns the address string for an outputs script pubkey" 232 | (sig->address (.getScriptPubKey o))) 233 | 234 | 235 | (defn to-addresses 236 | "Get the from addresses for a transaction" 237 | [tx] 238 | (set (remove nil? (map output->address (tx-outputs tx))))) 239 | 240 | (defn my-addresses 241 | "Return all the addresses in the given wallet" 242 | [wallet] 243 | (map ->address (keychain wallet))) 244 | 245 | (defn for-me? 246 | "Does this transaction belong to our wallet?" 247 | [tx wallet] 248 | (not (empty? (intersection (set (my-addresses wallet)) (to-addresses tx))))) 249 | 250 | (defn block->map 251 | "Turns a stored block into a subset of the JSON as used in Bitcoind's JSON-RPC inteface" 252 | [sb] 253 | (let [block (.getHeader sb)] 254 | { :hash (.getHashAsString block) 255 | :merkleroot (str (.getMerkleRoot block)) 256 | :nonce (.getNonce block), 257 | :difficulty (.getDifficultyTargetAsInteger block) 258 | :tx (map #(.getHashAsString %) (.getTransactions block)) 259 | :previousblockhash (str (.getPrevBlockHash block)) 260 | :time (.getTimeSeconds block) 261 | :height (.getHeight sb)})) 262 | 263 | (defn output->map 264 | [o i] 265 | { :index i 266 | :value (long (.getValue o)) 267 | ; :script (.getScriptBytes o) 268 | :address (output->address o)}) 269 | 270 | (defn input->output 271 | "attempts to find a connected output for a given input" 272 | [i] 273 | (if-let [op (.getOutpoint i)] 274 | (.getConnectedOutput op))) 275 | 276 | (defn input->value 277 | "attempts to find the value of a given input" 278 | [i] 279 | (if-let [o (input->output i)] 280 | (.getValue o) 281 | 0)) 282 | 283 | (defn input->map 284 | [i] 285 | (let [op (.getOutpoint i)] 286 | (if-let [o (.getConnectedOutput op)] 287 | (assoc (output->map o (.getIndex op)) 288 | :tx (str (.getHash op))) 289 | {:tx (str (.getHash op)) 290 | :index (.getIndex op) 291 | :address (str (.getFromAddress i))}))) 292 | 293 | (defn tx-fees 294 | "Don't trust this just yet" 295 | [tx] 296 | (- (reduce + (map #(input->value %) (.getInputs tx))) 297 | (reduce + (map #(.getValue %) (.getOutputs tx))))) 298 | 299 | (defn tx->map 300 | "Turns a Transaction into a map" 301 | ([tx] 302 | {:time (/ (.getTime (.getUpdateTime tx)) 1000) 303 | :outputs 304 | (map output->map 305 | (.getOutputs tx) 306 | (range (count (.getOutputs tx)))) 307 | :inputs (map input->map (.getInputs tx)) 308 | :confirmations (.getDepthInBlocks (.getConfidence tx)) 309 | ;:fees (tx-fees tx) 310 | :txid (str (.getHash tx))}) 311 | ([sb tx] 312 | (let [block (.getHeader sb)] 313 | (merge (tx->map tx) 314 | {:blockhash (.getHashAsString block) 315 | :blocktime (.getTimeSeconds block)})))) 316 | 317 | (defn wallet-tx->map 318 | "Turns a Wallets Transaction into a map" 319 | ([wallet tx] 320 | {:time (/ (.getTime (.getUpdateTime tx)) 1000) 321 | :amount (.getValue tx wallet) 322 | :details (reduce #(assoc % 323 | (key %2) 324 | (reduce + (map :value (val %2)))) {} 325 | (group-by :address 326 | (clojure.set/union 327 | (map #(assoc % :value (- (:value %))) 328 | (map output->map (filter #(not (.isMine % wallet)) (.getOutputs tx)))) 329 | (map input->map (filter #(not (.isMine (.getConnectedOutput (.getOutpoint %)) wallet)) (.getInputs tx)))))) 330 | :confirmations (.getDepthInBlocks (.getConfidence tx)) 331 | ;:fees (tx-fees tx) 332 | :txid (str (.getHash tx))}) 333 | ([wallet sb tx] 334 | (let [block (.getHeader sb)] 335 | (merge (wallet-tx->map wallet tx) 336 | {:blockhash (.getHashAsString block) 337 | :blocktime (.getTimeSeconds block)})))) 338 | 339 | (defn download-listener 340 | [pg] 341 | (.addEventListener pg 342 | (com.google.bitcoin.core.DownloadListener.))) 343 | 344 | (defn on-tx-broadcast 345 | "Listen to all transactions broadcast out to the network" 346 | ([f] 347 | (on-tx-broadcast @current-pg f)) 348 | ([pg f] 349 | (.addEventListener pg 350 | (proxy 351 | [com.google.bitcoin.core.AbstractPeerEventListener][] 352 | (onTransaction [peer tx] (f peer tx)))))) 353 | 354 | (defn on-coins-received 355 | "calls f with the transaction prev balance and new balance" 356 | [wallet f] 357 | (.addEventListener wallet 358 | (proxy 359 | [com.google.bitcoin.core.AbstractWalletEventListener][] 360 | (onCoinsReceived [w tx prev-balance new-balance] 361 | (if (= wallet w) 362 | (f tx prev-balance new-balance)))))) 363 | 364 | (defn on-tx-broadcast-to-wallet 365 | "calls f with the peer and transaction if transaction is received to the given wallet. 366 | This is called before the transaction is included in a block. Use it for SatoshiDice like applications" 367 | [wallet f] 368 | (on-tx-broadcast (fn [peer tx] 369 | (if (for-me? tx wallet) 370 | (f peer tx))))) 371 | 372 | (defn on-tx-broadcast-to-address 373 | "calls f with the peer and transaction if transaction is received to the given wallet. 374 | This is called before the transaction is included in a block. Use it for SatoshiDice like applications" 375 | [address f] 376 | (let [address (-> address)] 377 | (on-tx-broadcast (fn [peer tx] 378 | (if ((to-addresses tx) address) 379 | (f peer tx)))))) 380 | 381 | (defn send-coins 382 | "Send a value to a single recipient." 383 | [wallet to amount] 384 | (.sendCoins wallet @current-pg (->address to) (biginteger amount))) 385 | 386 | 387 | (defn ping-service 388 | "receive coins on address and return them immediately" 389 | ([kp] 390 | (ping-service (kp->wallet kp) kp)) 391 | ([wallet kp] 392 | (println "starting ping service on address: " (->address kp) ) 393 | (on-tx-broadcast-to-wallet wallet 394 | (fn [peer tx] 395 | (let [from (sender tx) 396 | amount (amount-received tx wallet)] 397 | (let [t2 (send-coins wallet from amount)] 398 | (print "Sent to: " (->address from)) 399 | (prn t2))))))) 400 | 401 | (defn download-block-chain 402 | "download block chain" 403 | ( [] (download-block-chain @current-pg)) 404 | ( [pg] 405 | (future (.downloadBlockChain pg)))) 406 | 407 | (defn start 408 | "start downloading regular block chain" 409 | ([] 410 | (if (nil? @current-pg) (init)) 411 | (start (peer-group))) 412 | ([pg] 413 | (.start pg))) 414 | 415 | 416 | (defn start-full 417 | "start downloading full block chain" 418 | [] 419 | (if (nil? @current-pg) (init-full)) 420 | (start @current-pg)) 421 | 422 | -------------------------------------------------------------------------------- /src/bitcoin/deterministic.clj: -------------------------------------------------------------------------------- 1 | (ns bitcoin.deterministic 2 | (:require [bitcoin.core :as btc]) 3 | (:import (com.google.bitcoin.crypto HDKeyDerivation HDUtils DeterministicKey) 4 | (java.security SecureRandom) 5 | (bitcoin.core Addressable))) 6 | 7 | 8 | (defn secure-random 9 | [] 10 | (SecureRandom.)) 11 | 12 | (defonce prng (atom (secure-random))) 13 | 14 | 15 | (defn create-master-key 16 | "Create a deterministic master" 17 | ([] 18 | (create-master-key (.generateSeed @prng 128))) 19 | ([seed] 20 | (HDKeyDerivation/createMasterPrivateKey seed))) 21 | 22 | (defn ->priv-bytes 23 | [dk] 24 | (.getPrivKeyBytes dk)) 25 | 26 | (defn ->pub-bytes 27 | [dk] 28 | (.getPubKeyBytes dk)) 29 | 30 | (defn ->chain-code 31 | "Get the chain key used for given deterministic key" 32 | [dk] 33 | (.getChainCode dk)) 34 | 35 | (defn dk->kp [dk] 36 | (.toECKey dk)) 37 | 38 | (extend-type com.google.bitcoin.crypto.DeterministicKey btc/Addressable 39 | (btc/->address 40 | ([dk] (btc/->address dk (btc/net))) 41 | ([dk network] (.toAddress (dk->kp dk) network)))) 42 | 43 | (defn serialize-pub [dk] 44 | (.serializePubB58 dk)) 45 | 46 | (defn serialize-priv [dk] 47 | (.serializePrivB58 dk)) 48 | 49 | (defn recreate-master-key 50 | "Create the master key from the public key and chain code" 51 | [priv chain] 52 | (HDKeyDerivation/createMasterPrivKeyFromBytes priv chain)) 53 | 54 | (defn recreate-master-pub-key 55 | "Create the master key from the public key and chain code" 56 | [pub chain] 57 | (HDKeyDerivation/createMasterPubKeyFromBytes pub chain)) 58 | 59 | (defn derive-key 60 | "Derive a key for the derived key" 61 | [dk i] 62 | (HDKeyDerivation/deriveChildKey dk (.intValue i))) 63 | 64 | (defn derive-from-path 65 | "Derive a key from a master key given a path" 66 | [mk path] 67 | (if (and path mk) 68 | (let [items (clojure.string/split path #"/")] 69 | (if (< (count items) 2) 70 | mk 71 | (reduce #(derive-key %1 (Long/parseLong %2)) mk (rest items)))))) 72 | 73 | (defn ->pub-only 74 | "Returns a derived key with only the public key part" 75 | [dk] 76 | (.getPubOnly dk)) 77 | 78 | (defn ->path 79 | "Returns the path from the master key to the given key" 80 | [dk] 81 | (.getPath dk)) 82 | -------------------------------------------------------------------------------- /test/bitcoin/test/core.clj: -------------------------------------------------------------------------------- 1 | (ns bitcoin.test.core 2 | (:use [bitcoin.core] 3 | [bux.currencies :only [BTC]] 4 | [clojure.test] 5 | [clojure.java.io :only [as-file delete-file]])) 6 | 7 | 8 | (deftest should-have-default-net 9 | (is (= (.getId (net)) "org.bitcoin.production") "Should default to prodNet")) 10 | 11 | (deftest should-create-key 12 | (let [kp (create-keypair)] 13 | (is (instance? com.google.bitcoin.core.ECKey kp) "Should create key"))) 14 | 15 | (deftest should-decode-key 16 | (let [kp (->kp "5KQ3SHTDHfU6XhBp7sSCbUoMpiZQKfKc7jVjAb6rHiegq1m2VWq")] 17 | (is (instance? com.google.bitcoin.core.ECKey kp) "Should create key") 18 | (is (= "5KQ3SHTDHfU6XhBp7sSCbUoMpiZQKfKc7jVjAb6rHiegq1m2VWq" (->private kp))))) 19 | 20 | (deftest should-create-address 21 | (is (instance? com.google.bitcoin.core.Address (->address (create-keypair) (prodNet))) "Should create address for keypair and network") 22 | (is (instance? com.google.bitcoin.core.Address (->address (create-keypair))) "Should create address for keypair default prod network") 23 | (is (instance? com.google.bitcoin.core.Address (->address (.getPubKey (create-keypair)))) "Should create address for public key default prod network") 24 | (is (instance? com.google.bitcoin.core.Address (->address "17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL" (prodNet))) "Should create address from string and network") 25 | (is (instance? com.google.bitcoin.core.Address (->address "17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL")) "Should create address from string") 26 | (is (= (->address "17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL") 27 | (->address (->address "17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL"))) "Should return itself for an instance of Address")) 28 | 29 | (deftest should-create-wallet 30 | (let [wal (create-wallet)] 31 | (is (instance? com.google.bitcoin.core.Wallet wal)) 32 | (let [kc (keychain wal)] 33 | (is (= 0 (count kc)))))) 34 | 35 | (deftest should-add-keypair 36 | (let [kp (create-keypair) 37 | wal (add-keypair (create-wallet) kp)] 38 | (is (instance? com.google.bitcoin.core.Wallet wal)) 39 | (let [kc (keychain wal)] 40 | (is (= 1 (count kc))) 41 | (is (= kp (first kc)))))) 42 | 43 | 44 | (deftest should-create-and-load-wallet 45 | (let [ filename "./test.wallet" 46 | _ (delete-file (as-file filename) true) 47 | wal (open-wallet filename)] 48 | (is (instance? com.google.bitcoin.core.Wallet wal)) 49 | (let [kc (keychain wal) 50 | kp (first kc)] 51 | (is (= 1 (count kc))) 52 | (is (instance? com.google.bitcoin.core.ECKey kp) 53 | (is (= (str kp) (str (first (keychain (open-wallet filename)))))))))) 54 | 55 | 56 | (deftest should-convert-nano-to-btc 57 | (is (= (BTC 0) (->BTC 0))) 58 | (is (= (BTC 1.23) (->BTC 123000000)))) -------------------------------------------------------------------------------- /test/bitcoin/test/deterministic.clj: -------------------------------------------------------------------------------- 1 | (ns bitcoin.test.deterministic 2 | (:use bitcoin.deterministic 3 | clojure.test) 4 | (:require [bitcoin.core :as btc])) 5 | 6 | 7 | (defn same-key? 8 | ([a b] 9 | (same-key? a b serialize-pub)) 10 | ([a b t] 11 | (is (= (t a) (t b))))) 12 | 13 | (deftest creating-master-key 14 | (let [dk (create-master-key) 15 | cc (->chain-code dk) 16 | priv (->priv-bytes dk) 17 | pub (->pub-bytes dk)] 18 | (is dk) 19 | (is (= (serialize-pub (recreate-master-key priv cc)) 20 | (serialize-pub dk))) 21 | (is (= (serialize-priv (recreate-master-key priv cc)) 22 | (serialize-priv dk))) 23 | (is (= (serialize-pub (recreate-master-pub-key pub cc)) 24 | (serialize-pub dk))) 25 | (is (= (btc/->address (recreate-master-pub-key pub cc)) 26 | (btc/->address dk))))) 27 | 28 | 29 | (deftest hierarchies 30 | (let [mk (create-master-key)] 31 | (same-key? (derive-from-path mk "M") mk) 32 | (same-key? (derive-from-path mk "M/1") (derive-key mk 1)) 33 | (same-key? (derive-from-path mk "M/2") (derive-key mk 2)) 34 | (same-key? (derive-from-path mk "M/1/3") (derive-key (derive-key mk 1) 3)) 35 | 36 | (let [pk (->pub-only mk)] 37 | (same-key? (derive-from-path mk "M") (derive-from-path pk "M")) 38 | (same-key? (derive-from-path mk "M/1") (derive-from-path pk "M/1")) 39 | (same-key? (derive-from-path mk "M/2") (derive-from-path pk "M/2")) 40 | (same-key? (derive-from-path mk "M/1/3") (derive-from-path pk "M/1/3"))))) 41 | --------------------------------------------------------------------------------