├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README.org ├── doc └── intro.md ├── img └── millions.jpg ├── project.clj ├── src └── io │ └── johnwalker │ ├── bitcoin_protocol.clj │ └── bitcoin_protocol │ └── gloss │ ├── extension.clj │ └── extension │ └── codecs.clj └── test └── io └── johnwalker └── bitcoin_protocol_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | jdk: 4 | - openjdk6 5 | - openjdk7 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Issues and pull requests are welcome! I'll respond between five minutes and a week. 4 | 5 | [Gloss](https://github.com/ztellman/gloss) is a foundation for this library. Any improvements there is also a really big help. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of Washington and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bitcoin-protocol 2 | 3 | This is an implementation of the [Bitcoin networking protocol](https://en.bitcoin.it/wiki/Protocol_specification). It can 4 | be used to communicate with peers within Bitcoin networks. The 5 | library handles deterministic fields like checksums and lengths, but 6 | the programmer must specify non-deterministic fields like nonces. 7 | 8 | Documentation to come. Mostly untested goodness at the moment. 9 | 10 | ```clojure 11 | ;; Latest version on Clojars 12 | [io.johnwalker/bitcoin-protocol "0.17.4"] 13 | ``` 14 | 15 | ## Usage with Aleph 16 | 17 | [Aleph](https://github.com/ztellman/aleph) has support for decoding of the wire automatically. Suppose 18 | you are connecting to a Bitcoin peer. Then you can communicate him 19 | in the usual manner with: 20 | 21 | ```clojure 22 | (require '[io.johnwalker/bitcoin-protocol :as bitcoin]) 23 | (def ch 24 | (wait-for-result 25 | (tcp-client {:host "localhost", 26 | :port 10000, 27 | :frame bitcoin/network-protocol}))) 28 | (enqueue ch {:command :getaddr 29 | :magic :main}) 30 | (wait-for-message ch) 31 | ;; => {:magic :main 32 | ;; :command :addr 33 | ;; :checksum [...] 34 | ;; :addrs [blah blah blah]} 35 | ``` 36 | 37 | ## Examples 38 | 39 | The two functions you care most about are `write-message` and 40 | `read-message.` `write-message` will convert hashmaps to 41 | Bytebuffers. `read-message` will bring Bytebuffers back to 42 | hashmaps. If you use aleph, then you don't have to care about 43 | either of these. 44 | 45 | ### Build a version message 46 | 47 | ```clojure 48 | (require '[bitcoin-protocol.messages :refer [write-message read-message]]) 49 | 50 | (def my-version-messages (write-message 51 | {:command "version" 52 | :magic :main 53 | :version 60001 54 | :services 1 55 | :timestamp 0x50D0B211 56 | :addr-recv {:services 1 :ip "0.0.0.0" :port 0} 57 | :addr-from {:services 1 :ip "0.0.0.0" :port 0} 58 | :nonce 0x6517E68C5DB32E3B 59 | :user-agent "/Satoshi:0.7.2/" 60 | :start-height 212672 61 | :relay true})) 62 | 63 | ;; # 64 | ``` 65 | 66 | ### Read the version message back 67 | 68 | `Read-message` works for other messages, like getaddr, ping, pong 69 | and so forth. 70 | 71 | ```clojure 72 | (def read-it-back (read-message my-version-message)) 73 | ``` 74 | 75 | ```clojure 76 | {:addr-from {:port 0 :ip "0.0.0.0" :services 1N} 77 | :user-agent "/Satoshi:0.7.2/" 78 | :command "version" 79 | :magic :main 80 | :start-height 212672 81 | :relay true 82 | :checksum [133 39 57 227] 83 | :length 101 84 | :version 60001 85 | :timestamp 1355854353 86 | :nonce 7284544412836900411N 87 | :services 1N 88 | :addr-recv {:port 0 :ip "0.0.0.0" :services 1N} 89 | ``` 90 | 91 | ### Verify the insides 92 | 93 | Don't be shy when it comes to verifying the encoding. The raw bytes 94 | are easy to see (and there are better formatters in Clojure land). 95 | 96 | ```clojure 97 | (defn- str-bytes [h] 98 | (clojure.string/trimr 99 | (apply str 100 | (for [x (range (.remaining h))] 101 | (format "%02X " (.get h x)))))) 102 | 103 | (def raw-bytes (str-bytes my-version-message) 104 | ``` 105 | 106 | ### The Rest 107 | 108 | Everything else works just like the version message. Some of them 109 | haven't been tested yet, however. If you run into an error, please 110 | report it to me. I'll be especially happy to accept `test.check` or 111 | unit tests that demonstrate the failure. 112 | 113 | Presently, `read-message` and `write-message` is expected to work 114 | for 115 | 116 | ``` 117 | version 118 | verack 119 | addr 120 | getaddr 121 | inv 122 | ping 123 | pong 124 | reject 125 | ``` 126 | 127 | while these are around, but are untested. 128 | 129 | ``` 130 | alert 131 | block 132 | getdata 133 | notfound 134 | getblocks 135 | getheaders 136 | tx 137 | ``` 138 | 139 | # License 140 | 141 | Copyright © 2014 John Walker 142 | 143 | Distributed under the Eclipse Public License version 1.0. 144 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * bitcoin-protocol 2 | 3 | [[img/millions.jpg]] 4 | 5 | This is an implementation of the [[https://en.bitcoin.it/wiki/Protocol_specification][Bitcoin networking protocol]]. It can 6 | be used to communicate with peers within Bitcoin networks. The 7 | library handles deterministic fields like checksums and lengths, but 8 | the programmer must specify non-deterministic fields like nonces. 9 | 10 | Documentation to come. Mostly untested goodness at the moment. 11 | 12 | #+BEGIN_SRC clojure 13 | ;; Latest version on Clojars 14 | [io.johnwalker/bitcoin-protocol "0.17.4"] 15 | #+END_SRC 16 | 17 | ** Usage with Aleph 18 | [[https://github.com/ztellman/aleph][Aleph]] has support for decoding of the wire automatically. Suppose 19 | you are connecting to a Bitcoin peer. Then you can communicate him 20 | in the usual manner with: 21 | 22 | #+BEGIN_SRC clojure 23 | (require '[io.johnwalker/bitcoin-protocol :as bitcoin]) 24 | (def ch 25 | (wait-for-result 26 | (tcp-client {:host "localhost", 27 | :port 10000, 28 | :frame bitcoin/network-protocol}))) 29 | (enqueue ch {:command :getaddr 30 | :magic :main}) 31 | (wait-for-message ch) 32 | ;; => {:magic :main 33 | ;; :command :addr 34 | ;; :checksum [...] 35 | ;; :addrs [blah blah blah]} 36 | #+END_SRC 37 | 38 | ** Examples 39 | 40 | The two functions you care most about are =write-message= and 41 | =read-message.= =write-message= will convert hashmaps to 42 | Bytebuffers. =read-message= will bring Bytebuffers back to 43 | hashmaps. If you use aleph, then you don't have to care about 44 | either of these. 45 | 46 | *** Build a version message 47 | 48 | #+BEGIN_SRC clojure 49 | (require '[bitcoin-protocol.messages :refer [write-message read-message]]) 50 | 51 | (def my-version-messages (write-message 52 | {:command "version" 53 | :magic :main 54 | :version 60001 55 | :services 1 56 | :timestamp 0x50D0B211 57 | :addr-recv {:services 1 :ip "0.0.0.0" :port 0} 58 | :addr-from {:services 1 :ip "0.0.0.0" :port 0} 59 | :nonce 0x6517E68C5DB32E3B 60 | :user-agent "/Satoshi:0.7.2/" 61 | :start-height 212672 62 | :relay true})) 63 | 64 | ;; # 65 | #+END_SRC 66 | 67 | *** Read the version message back 68 | =Read-message= works for other messages, like getaddr, ping, pong 69 | and so forth. 70 | 71 | #+BEGIN_SRC clojure 72 | (def read-it-back (read-message my-version-message)) 73 | #+END_SRC 74 | #+BEGIN_SRC clojure 75 | {:addr-from {:port 0 :ip "0.0.0.0" :services 1N} 76 | :user-agent "/Satoshi:0.7.2/" 77 | :command "version" 78 | :magic :main 79 | :start-height 212672 80 | :relay true 81 | :checksum [133 39 57 227] 82 | :length 101 83 | :version 60001 84 | :timestamp 1355854353 85 | :nonce 7284544412836900411N 86 | :services 1N 87 | :addr-recv {:port 0 :ip "0.0.0.0" :services 1N} 88 | #+END_SRC 89 | 90 | *** Verify the insides 91 | Don't be shy when it comes to verifying the encoding. The raw bytes 92 | are easy to see (and there are better formatters in Clojure land). 93 | 94 | #+BEGIN_SRC clojure 95 | (defn- str-bytes [h] 96 | (clojure.string/trimr 97 | (apply str 98 | (for [x (range (.remaining h))] 99 | (format "%02X " (.get h x)))))) 100 | 101 | (def raw-bytes (str-bytes my-version-message) 102 | #+END_SRC 103 | 104 | *** The Rest 105 | Everything else works just like the version message. Some of them 106 | haven't been tested yet, however. If you run into an error, please 107 | report it to me. I'll be especially happy to accept =test.check= or 108 | unit tests that demonstrate the failure. 109 | 110 | Presently, =read-message= and =write-message= is expected to work 111 | for 112 | 113 | #+BEGIN_SRC 114 | version 115 | verack 116 | addr 117 | getaddr 118 | inv 119 | ping 120 | pong 121 | reject 122 | #+END_SRC 123 | 124 | while these are around, but are untested. 125 | 126 | #+BEGIN_SRC 127 | alert 128 | block 129 | getdata 130 | notfound 131 | getblocks 132 | getheaders 133 | tx 134 | #+END_SRC 135 | 136 | * License 137 | 138 | Copyright © 2014 John Walker 139 | 140 | Distributed under the Eclipse Public License version 1.0. 141 | -------------------------------------------------------------------------------- /doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to bitcoin-protocol 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /img/millions.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalker/bitcoin-protocol/4301aeb20f2b958c4cd399d951b67435dc74d459/img/millions.jpg -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject io.johnwalker/bitcoin-protocol "0.17.4" 2 | :description "An implementation of the Bitcoin networking protocol." 3 | :url "https://github.com/johnwalker/bitcoin-protocol" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.6.0"] 7 | [gloss "0.2.2"]] 8 | :profiles {:dev {:dependencies [[org.clojure/tools.namespace "0.2.5"] 9 | [org.clojure/test.check "0.5.9"]]}}) 10 | -------------------------------------------------------------------------------- /src/io/johnwalker/bitcoin_protocol.clj: -------------------------------------------------------------------------------- 1 | (ns io.johnwalker.bitcoin-protocol 2 | (:require [io.johnwalker.bitcoin-protocol.gloss.extension :as e] 3 | [clojure.string :as str] 4 | [gloss.core :refer :all] 5 | [gloss.io :refer :all])) 6 | 7 | (let [u16-le (compile-frame :uint16-le) 8 | u32-le (compile-frame :uint32-le) 9 | u64-le (compile-frame :uint64-le)] 10 | (defcodec varint (header :ubyte 11 | (fn [h] 12 | (case h 13 | 0xfd u16-le 14 | 0xfe u32-le 15 | 0xff u64-le 16 | (compile-frame nil-frame 17 | identity 18 | (constantly h)))) 19 | (fn [b] 20 | (condp >= b 21 | 0xfc b 22 | 0xffff 0xfd 23 | 0xffffffff 0xfe 24 | 0xff))))) 25 | 26 | 27 | (defcodec varstr (compile-frame (finite-frame 28 | varint 29 | (string :ascii)) 30 | identity 31 | (fn [s] 32 | (or s "")))) 33 | 34 | 35 | (defcodec ip-addr (compile-frame (repeat 16 :ubyte) 36 | (fn [s] 37 | (concat (repeat 10 0) 38 | (repeat 2 0xFF) 39 | (map 40 | #(Integer/parseInt %) 41 | (str/split s #"\.")))) 42 | (fn [b] 43 | (apply str (interpose "." (take-last 4 b)))))) 44 | 45 | (defcodec net-addr (ordered-map 46 | :services :uint64-le 47 | :ip ip-addr 48 | :port :uint16-be)) 49 | 50 | 51 | (defcodec net-addrt (ordered-map :time :uint32-le 52 | :services :uint64-le 53 | :ip ip-addr 54 | :port :uint16-be)) 55 | 56 | 57 | (defcodec magic (enum :uint32-le 58 | {:main 0xd9b4bef9 59 | :namecoin 0xfeb4bef9 60 | :testnet 0xdab5bffa 61 | :testnet3 0x0709110b})) 62 | 63 | 64 | (defcodec command (compile-frame 65 | (string :ascii :length 12) 66 | (fn [s] 67 | (->> 0 char 68 | repeat 69 | (concat s) 70 | (take 12) 71 | (apply str))) 72 | (fn [s] 73 | (->> s 74 | (take-while (fn [s] (not= s (char 0)))) 75 | (apply str))))) 76 | 77 | (defn sha-256 [bytes] 78 | (.digest (java.security.MessageDigest/getInstance "SHA-256") bytes)) 79 | 80 | (defn gen-checksum [n bytes] 81 | (take n (sha-256 (sha-256 bytes)))) 82 | 83 | (defcodec checksum 84 | (compile-frame [:ubyte :ubyte :ubyte :ubyte])) 85 | 86 | (defcodec relay (enum :byte {true 1 87 | false 0})) 88 | 89 | (defcodec version-payload (compile-frame (ordered-map 90 | :version :int32-le 91 | :services :uint64-le 92 | :timestamp :int64-le 93 | :addr-recv net-addr 94 | :addr-from net-addr 95 | :nonce :uint64-le 96 | :user-agent varstr 97 | :start-height :int32-le 98 | :relay relay))) 99 | 100 | (defcodec verack-payload nil-frame) 101 | 102 | (defcodec addr-payload (compile-frame 103 | (ordered-map :addrs 104 | (repeated net-addrt :prefix varint)))) 105 | 106 | (defcodec getaddr-payload nil-frame) 107 | 108 | (defcodec ping-payload (ordered-map :payload :uint64-le)) 109 | 110 | (defcodec pong-payload (ordered-map :payload :uint64-le)) 111 | 112 | (def reject-keyword->value {:malformed 0x01 113 | :invalid 0x10 114 | :obsolete 0x11 115 | :duplicate 0x12 116 | :nonstandard 0x40 117 | :dust 0x41 118 | :insufficient-fee 0x42 119 | :checkpoint 0x43}) 120 | 121 | (def reject-keyword->str {:malformed "REJECT_MALFORMED" 122 | :invalid "REJECT_INVALID" 123 | :obsolete "REJECT_OBSOLETE" 124 | :duplicate "REJECT_DUPLICATE" 125 | :nonstandard "REJECT_NONSTANDARD" 126 | :dust "REJECT_DUST" 127 | :insufficient-fee "REJECT_INSUFFICIENTFEE" 128 | :checkpoint "REJECT_CHECKPOINT"}) 129 | 130 | 131 | (defcodec reject-payload (compile-frame (ordered-map :message varstr 132 | :code :byte 133 | :reason varstr))) 134 | 135 | 136 | (defn reject-map [k message] 137 | (when-let [code (k reject-keyword->value)] 138 | {:message message 139 | :code (k reject-keyword->value) 140 | :reason (k reject-keyword->str)})) 141 | 142 | (defcodec inv-vector (compile-frame (ordered-map :type (enum :uint32-le 143 | {:error 0 144 | :msg-tx 1 145 | :msg-block 2}) 146 | :hash (repeat 32 :byte)) 147 | (fn [{:keys [type bytes]}] 148 | {:type type 149 | :hash (gen-checksum 32 bytes)}) 150 | identity)) 151 | 152 | 153 | (defcodec block-header (compile-frame (ordered-map :version :uint32-le 154 | :prev-block (repeat 32 :byte) 155 | :merkle-root (repeat 32 :byte) 156 | :timestamp :uint32-le 157 | :bits :uint32-le 158 | :nonce :uint32-le 159 | :txn-count varint))) 160 | 161 | 162 | (defcodec inv-payload (compile-frame 163 | (ordered-map 164 | :inv-vectors (repeated inv-vector 165 | :prefix varint)))) 166 | 167 | (defcodec getdata-payload inv-payload) 168 | 169 | (defcodec notfound-payload inv-payload) 170 | 171 | 172 | (defcodec getblocks-payload (ordered-map 173 | :version :uint32-le 174 | :hashes (repeated inv-vector 175 | ;; The spec isn't clear here. 176 | ;; It might be necessary to 177 | ;; decrement the varint prefix. 178 | :prefix varint))) 179 | 180 | (defcodec getheaders-payload getblocks-payload) 181 | 182 | 183 | (defcodec outpoint (ordered-map 184 | :prev-hash (repeat 32 :byte) 185 | :index :uint32-le)) 186 | 187 | (defcodec txin (ordered-map 188 | :previous outpoint 189 | :signature-script (repeated :byte :prefix varint) 190 | :sequence :uint32-le)) 191 | 192 | (defcodec txout (ordered-map 193 | :value :int64-le 194 | ;; I'm pretending uchars don't exist for now 195 | :pk-script (repeated :byte :prefix varint))) 196 | 197 | 198 | (defcodec tx-payload (ordered-map 199 | :version :uint32-le 200 | :txin-vec (repeated :byte :prefix txin) 201 | :txin-vec (repeated :byte :prefix txout) 202 | :lock-time :uint32-le)) 203 | 204 | 205 | (defcodec block-payload (ordered-map 206 | :version :uint32-le 207 | :prev-block (repeat 32 :byte) 208 | :merkle-root (repeat 32 :byte) 209 | :timestamp :int32-le 210 | :bits :int32-le 211 | :nonce :int32-le 212 | :txns (repeated tx-payload :prefix varint))) 213 | 214 | 215 | (defcodec alert-payload (ordered-map 216 | :version :int32-le 217 | :relay-until :int64-le 218 | :expiration :int64-le 219 | :id :int32-le 220 | :cancel :int32-le 221 | :set-cancel (repeated :int32-le :prefix varint) 222 | :min-ver :int32-le 223 | :max-ver :int32-le 224 | :set-subver (repeated varstr :prefix varint) 225 | :priority :int32-le 226 | :comment varstr 227 | :status-bar varstr 228 | :reserved varstr)) 229 | 230 | 231 | (def command->payload {"version" version-payload 232 | "verack" verack-payload 233 | "addr" addr-payload 234 | "alert" alert-payload 235 | "block" block-payload 236 | "getaddr" getaddr-payload 237 | "inv" inv-payload 238 | "getdata" getdata-payload 239 | "notfound" notfound-payload 240 | "getblocks" getblocks-payload 241 | "getheaders" getheaders-payload 242 | "ping" ping-payload 243 | "pong" pong-payload 244 | "reject" reject-payload 245 | "tx" tx-payload}) 246 | 247 | (let [empty-byte-array (-> [] to-byte-buffer .array)] 248 | (defcodec network-protocol 249 | (e/header (ordered-map :magic magic 250 | :command command 251 | :length :uint32-le 252 | :checksum checksum) 253 | (fn [{:keys [command]}] 254 | (-> command name command->payload)) 255 | (fn [{:keys [command magic] :as b}] 256 | ;; TODO: Revisit with a header function that passes the 257 | ;; raw bytes to body->header. "encoding" should disappear. 258 | ;; 259 | ;; Also, this is really annoying. The version isn't in the 260 | ;; header. How do we handle multiple versions of the 261 | ;; bitcoin networking protocol? 262 | 263 | ;; -- johnwalker 264 | 265 | (let [first-encoding (-> command 266 | name 267 | command->payload 268 | (encode b)) 269 | 270 | ;; Keeps the empty payload case from exploding. 271 | second-encoding (if first-encoding 272 | (-> first-encoding contiguous .array) 273 | empty-byte-array)] 274 | {:magic magic 275 | :command (name command) 276 | :length (count second-encoding) 277 | :checksum (gen-checksum 4 second-encoding)}))))) 278 | 279 | 280 | (defn write-message 281 | "Write a bitcoin network message" 282 | [m] 283 | ;; TODO - asserts 284 | (contiguous (encode network-protocol m))) 285 | 286 | 287 | (defn read-message 288 | "Read a bitcoin network message" 289 | [x] 290 | (decode network-protocol x)) 291 | -------------------------------------------------------------------------------- /src/io/johnwalker/bitcoin_protocol/gloss/extension.clj: -------------------------------------------------------------------------------- 1 | (ns io.johnwalker.bitcoin-protocol.gloss.extension 2 | (:require [io.johnwalker.bitcoin-protocol.gloss.extension.codecs :as codecs] 3 | [gloss.core :refer [compile-frame]])) 4 | 5 | (defn header 6 | "I copied ztellman here. codecs/header is slightly different." 7 | [frame header->body body->header] 8 | (codecs/header 9 | (compile-frame frame) 10 | header->body 11 | body->header)) 12 | -------------------------------------------------------------------------------- /src/io/johnwalker/bitcoin_protocol/gloss/extension/codecs.clj: -------------------------------------------------------------------------------- 1 | (ns io.johnwalker.bitcoin-protocol.gloss.extension.codecs 2 | (:use [gloss.data bytes string primitives] 3 | [gloss.core protocols structure formats]) 4 | (:require [gloss.core :refer [ordered-map]])) 5 | 6 | (defn header 7 | "It's the same as the gloss.core.codecs/header, but in read-codec 8 | the header and body are merged." 9 | [codec header->body body->header] 10 | (let [read-codec (compose-callback 11 | codec 12 | (fn [v b] 13 | (let [body (header->body v) 14 | [success m x] (read-bytes body b)] 15 | (if (and success (map? v)) 16 | [success (merge m v) x] 17 | [success m x]))))] 18 | (reify 19 | Reader 20 | (read-bytes [_ buf-seq] 21 | (read-bytes read-codec buf-seq)) 22 | Writer 23 | (sizeof [_] 24 | nil) 25 | (write-bytes [_ buf val] 26 | (let [header (body->header val) 27 | body (header->body header)] 28 | (if (and (sizeof codec) (sizeof body)) 29 | (with-buffer [buf (+ (sizeof codec) (sizeof body))] 30 | (write-bytes codec buf header) 31 | (write-bytes body buf val)) 32 | (concat 33 | (write-bytes codec buf header) 34 | (write-bytes body buf val)))))))) 35 | -------------------------------------------------------------------------------- /test/io/johnwalker/bitcoin_protocol_test.clj: -------------------------------------------------------------------------------- 1 | (ns io.johnwalker.bitcoin-protocol_test 2 | (:require [clojure.test :refer :all] 3 | [clojure.test.check :as tc] 4 | [clojure.test.check.clojure-test :refer [defspec]] 5 | [clojure.test.check.generators :as gen] 6 | [clojure.test.check.properties :as prop] 7 | [io.johnwalker.bitcoin-protocol :as pm] 8 | [gloss.io :refer :all])) 9 | 10 | (defn- str-bytes [h] 11 | (clojure.string/trimr 12 | (apply str 13 | (for [x (range (.remaining h))] 14 | (format "%02X " (.get h x)))))) 15 | 16 | (defn to-unsigned [x] 17 | (bit-and x 0xFF)) 18 | 19 | (defn expected-bytes [length] 20 | (+ (dec length) 21 | (* 2 length))) 22 | 23 | (deftest raw-varint-encoding 24 | (is (= (str-bytes (first (encode pm/varint 0x05))) "05")) 25 | (is (= (str-bytes (first (encode pm/varint 0x32))) "32")) 26 | (is (= (str-bytes (first (encode pm/varint 0xFC))) "FC")) 27 | (is (= (str-bytes (first (encode pm/varint 0xFD))) "FD FD 00")) 28 | (is (= (str-bytes (first (encode pm/varint 0xFF))) "FD FF 00")) 29 | (is (= (str-bytes (first (encode pm/varint 0xFFFE))) "FD FE FF")) 30 | (is (= (str-bytes (first (encode pm/varint 0xFFFF))) "FD FF FF")) 31 | (is (= (str-bytes (first (encode pm/varint 0x10000))) "FE 00 00 01 00")) 32 | (is (= (str-bytes (first (encode pm/varint 0x100000))) "FE 00 00 10 00")) 33 | (is (= (str-bytes (first (encode pm/varint 0xFFFFFFFF))) "FE FF FF FF FF")) 34 | (is (= (str-bytes (first (encode pm/varint 0x100000000))) "FF 00 00 00 00 01 00 00 00")) 35 | (is (= (str-bytes (first (encode pm/varint 0xFFFFFFFFFF))) "FF FF FF FF FF FF 00 00 00"))) 36 | 37 | 38 | (defspec isomorphic-varint 39 | 150 40 | (prop/for-all [i gen/pos-int] 41 | (= i (decode pm/varint (encode pm/varint i))))) 42 | 43 | 44 | (deftest raw-varstr-encoding 45 | (let [[satoshi-varint satoshi-str] (encode pm/varstr "/Satoshi:0.7.2/")] 46 | ;; Length of "/Satoshi:0.7.2/" is 15 47 | (is (= (str-bytes satoshi-varint) "0F")) 48 | ;; Char array of "/Satoshi:0.7.2/" is correct 49 | (is (= (str-bytes satoshi-str) "2F 53 61 74 6F 73 68 69 3A 30 2E 37 2E 32 2F")))) 50 | 51 | 52 | (defspec isomorphic-varstr 53 | 150 54 | (prop/for-all [s gen/string-ascii] 55 | (= s (decode pm/varstr (encode pm/varstr s))))) 56 | 57 | 58 | (defspec isomorphic-ipaddr 59 | 150 60 | (prop/for-all [ip (gen/vector gen/byte 4)] 61 | (let [ubv (mapv to-unsigned ip) 62 | ip-str (apply str (interpose "." ubv))] 63 | (= ip-str (decode pm/ip-addr (encode pm/ip-addr ip-str)))))) 64 | 65 | 66 | (deftest raw-netaddrt-encoding 67 | (= (apply str-bytes (encode pm/net-addrt 68 | {:time 1 69 | :services 1 70 | :ip "192.168.1.1" 71 | :port 0xFF})) 72 | "01 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF C0 A8 01 01 00 FF")) 73 | 74 | (defspec isomorphic-netaddrt 75 | 150 76 | (prop/for-all [ip (gen/vector gen/byte 4) 77 | i gen/pos-int 78 | j gen/pos-int 79 | k gen/byte] 80 | (let [ubv (mapv to-unsigned ip) 81 | ip-str (apply str (interpose "." ubv)) 82 | k (to-unsigned k)] 83 | (= {:time i 84 | :services j 85 | :ip ip-str 86 | :port k} 87 | (decode pm/net-addrt (encode pm/net-addrt {:time i 88 | :services j 89 | :ip ip-str 90 | :port k})))))) 91 | 92 | (deftest raw-checksum 93 | ;; First four bytes of hello double SHA-256 is '95 95 C9 DF' 94 | (is (= (-> pm/checksum 95 | (encode (->> "hello" 96 | .getBytes 97 | (pm/gen-checksum 4))) 98 | first 99 | str-bytes) 100 | "95 95 C9 DF"))) 101 | 102 | 103 | (defspec isomorphic-checksum 104 | 150 105 | (prop/for-all [i (gen/vector gen/byte 4)] 106 | (let [ubv (mapv to-unsigned i)] 107 | (= ubv (decode pm/checksum (encode pm/checksum ubv)))))) 108 | 109 | 110 | (deftest version-payload 111 | ;; Example #1 on Wiki (with relay codec appended to end) 112 | ;; 0000 f9 be b4 d9 76 65 72 73 69 6f 6e 00 00 00 00 00 ....version..... 113 | ;; 0010 64 00 00 00 35 8d 49 32 62 ea 00 00 01 00 00 00 d...5.I2b....... 114 | ;; 0020 00 00 00 00 11 b2 d0 50 00 00 00 00 01 00 00 00 .......P........ 115 | ;; 0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ff ................ 116 | ;; 0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 117 | ;; 0050 00 00 00 00 00 00 00 00 ff ff 00 00 00 00 00 00 ................ 118 | ;; 0060 3b 2e b3 5d 8c e6 17 65 0f 2f 53 61 74 6f 73 68 ;..]...e./Satosh 119 | ;; 0070 69 3a 30 2e 37 2e 32 2f c0 3e 03 00 i:0.7.2/.>.. 120 | 121 | ;; Message Header: 122 | ;; F9 BE B4 D9 - Main network magic bytes 123 | ;; 76 65 72 73 69 6F 6E 00 00 00 00 00 - "version" command 124 | ;; 64 00 00 00 - Payload is 100 bytes long 125 | ;; 3B 64 8D 5A - payload checksum 126 | 127 | ;; Version message: 128 | ;; 62 EA 00 00 - 60002 (protocol version 60002) 129 | ;; 01 00 00 00 00 00 00 00 - 1 (NODE_NETWORK services) 130 | ;; 11 B2 D0 50 00 00 00 00 - Tue Dec 18 10:12:33 PST 2012 131 | ;; 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00 00 00 - Recipient address info - see Network Address 132 | ;; 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00 00 00 - Sender address info - see Network Address 133 | ;; 3B 2E B3 5D 8C E6 17 65 - Node ID 134 | ;; 0F 2F 53 61 74 6F 73 68 69 3A 30 2E 37 2E 32 2F - "/Satoshi:0.7.2/" sub-version string (string is 15 bytes long) 135 | ;; C0 3E 03 00 - Last block sending node has is block #212672 136 | 137 | (is (= (->> {:version 60002 :services 1 :timestamp 1355854353 138 | :addr-recv {:services 1 139 | :ip "0.0.0.0" 140 | :port 0} 141 | :addr-from {:services 1 142 | :ip "0.0.0.0" 143 | :port 0} 144 | :nonce 7284544412836900411 145 | :user-agent "/Satoshi:0.7.2/" 146 | :start-height 212672 147 | :relay true} 148 | (encode pm/version-payload) 149 | (map str-bytes) 150 | (interpose " ") 151 | (apply str)) 152 | (apply str (interpose " " '("62 EA 00 00" 153 | "01 00 00 00 00 00 00 00" 154 | "11 B2 D0 50 00 00 00 00" 155 | "01 00 00 00 00 00 00 00" 156 | "00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00 00 00" 157 | "01 00 00 00 00 00 00 00" 158 | "00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00 00 00" 159 | "3B 2E B3 5D 8C E6 17 65" 160 | "0F" 161 | "2F 53 61 74 6F 73 68 69 3A 30 2E 37 2E 32 2F" 162 | "C0 3E 03 00" 163 | "01")))))) 164 | 165 | 166 | (deftest version-message 167 | ;; NOTE: There just happens to be a collision between these two 168 | ;; examples. This is a version without the relay byte. 169 | 170 | ;; F9 BE B4 D9 - Main network magic bytes 171 | ;; 76 65 72 73 69 6F 6E 00 00 00 00 00 - "version" command 172 | ;; 64 00 00 00 - Payload is 100 bytes long 173 | ;; 3B 64 8D 5A - payload checksum 174 | 175 | ;; Version message: 176 | ;; 62 EA 00 00 - 60002 (protocol version 60002) 177 | ;; 01 00 00 00 00 00 00 00 - 1 (NODE_NETWORK services) 178 | ;; 11 B2 D0 50 00 00 00 00 - Tue Dec 18 10:12:33 PST 2012 179 | ;; 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00 00 00 - Recipient address info - see Network Address 180 | ;; 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00 00 00 - Sender address info - see Network Address 181 | ;; 3B 2E B3 5D 8C E6 17 65 - Node ID 182 | ;; 0F 2F 53 61 74 6F 73 68 69 3A 30 2E 37 2E 32 2F - "/Satoshi:0.7.2/" sub-version string (string is 15 bytes long) 183 | ;; C0 3E 03 00 - Last block sending node has is block #212672 184 | 185 | 186 | (= (apply str (interpose " " (map str-bytes (encode pm/network-protocol 187 | {:command "version" 188 | :magic :main 189 | :version 60001 190 | :services 1 191 | :timestamp 0x50D0B211 192 | :addr-recv {:services 1 :ip "0.0.0.0" :port 0} 193 | :addr-from {:services 1 :ip "0.0.0.0" :port 0} 194 | :nonce 0x6517E68C5DB32E3B 195 | :user-agent "/Satoshi:0.7.2/" 196 | :start-height 212672 197 | :relay true})))) 198 | (apply str (interpose " " '("F9 BE B4 D9" 199 | "76 65 72 73 69 6F 6E 00 00 00 00 00" 200 | "65" 201 | "85 27 39 E3" 202 | "61 EA 00 00" 203 | "01 00 00 00 00 00 00 00" 204 | "11 B2 D0 50 00 00 00 00" 205 | "01 00 00 00 00 00 00 00" 206 | "00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00" 207 | "00 00" 208 | "01 00 00 00 00 00 00 00" 209 | "00 00 00 00 00 00 00 00 00 00 FF FF 00 00 00 00" 210 | "00 00" 211 | "3B 2E B3 5D 8C E6 17 65" 212 | "0F" 213 | "2F 53 61 74 6F 73 68 69 3A 30 2E 37 2E 32 2F" 214 | "C0 3E 03 00" 215 | "01"))))) 216 | 217 | 218 | (deftest verack-message 219 | ;; Message header: 220 | ;; F9 BE B4 D9 - Main network magic bytes 221 | ;; 76 65 72 61 63 6B 00 00 00 00 00 00 - "verack" command 222 | ;; 00 00 00 00 - Payload is 0 bytes long 223 | ;; 5D F6 E0 E2 - Checksum 224 | (is (= (map str-bytes (encode pm/network-protocol 225 | {:magic :main 226 | :command "verack"})) 227 | '("F9 BE B4 D9" 228 | "76 65 72 61 63 6B 00 00 00 00 00 00" 229 | "00 00 00 00" 230 | "5D F6 E0 E2")))) 231 | 232 | 233 | (deftest addr-message 234 | (is (= (apply str (interpose " " (map str-bytes (encode pm/network-protocol {:magic :main 235 | :command "addr" 236 | :addrs [{:time 0x4D1015E2 :services 1 :ip "10.0.0.1" :port 8333}]})))) 237 | (apply str (interpose " " '("F9 BE B4 D9" 238 | "61 64 64 72 00 00 00 00 00 00 00 00" 239 | "1F 00 00 00" 240 | "ED 52 39 9B" 241 | "01" 242 | "E2 15 10 4D" 243 | "01 00 00 00 00 00 00 00" 244 | "00 00 00 00 00 00 00 00 00 00 FF FF 0A 00 00 01" 245 | "20 8D")))))) 246 | --------------------------------------------------------------------------------