btcruby 0.0.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +18 -0
  3. data/.travis.yml +7 -0
  4. data/FAQ.md +7 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +18 -0
  7. data/HOWTO.md +17 -0
  8. data/LICENSE +19 -0
  9. data/README.md +59 -0
  10. data/Rakefile +6 -0
  11. data/TODO.txt +40 -0
  12. data/bin/console +19 -0
  13. data/btcruby.gemspec +20 -0
  14. data/documentation/address.md +73 -0
  15. data/documentation/base58.md +52 -0
  16. data/documentation/block.md +127 -0
  17. data/documentation/block_header.md +120 -0
  18. data/documentation/constants.md +88 -0
  19. data/documentation/data.md +54 -0
  20. data/documentation/diagnostics.md +90 -0
  21. data/documentation/extensions.md +76 -0
  22. data/documentation/hash_functions.md +58 -0
  23. data/documentation/hash_id.md +22 -0
  24. data/documentation/index.md +230 -0
  25. data/documentation/key.md +177 -0
  26. data/documentation/keychain.md +180 -0
  27. data/documentation/network.md +75 -0
  28. data/documentation/opcode.md +220 -0
  29. data/documentation/openssl.md +7 -0
  30. data/documentation/p2pkh.md +71 -0
  31. data/documentation/p2sh.md +64 -0
  32. data/documentation/proof_of_work.md +84 -0
  33. data/documentation/script.md +280 -0
  34. data/documentation/signature.md +71 -0
  35. data/documentation/transaction.md +213 -0
  36. data/documentation/transaction_builder.md +188 -0
  37. data/documentation/transaction_input.md +133 -0
  38. data/documentation/transaction_output.md +130 -0
  39. data/documentation/wif.md +72 -0
  40. data/documentation/wire_format.md +70 -0
  41. data/lib/btcruby/address.rb +296 -0
  42. data/lib/btcruby/base58.rb +108 -0
  43. data/lib/btcruby/big_number.rb +47 -0
  44. data/lib/btcruby/block.rb +170 -0
  45. data/lib/btcruby/block_header.rb +231 -0
  46. data/lib/btcruby/constants.rb +59 -0
  47. data/lib/btcruby/currency_formatter.rb +64 -0
  48. data/lib/btcruby/data.rb +98 -0
  49. data/lib/btcruby/diagnostics.rb +92 -0
  50. data/lib/btcruby/errors.rb +8 -0
  51. data/lib/btcruby/extensions.rb +65 -0
  52. data/lib/btcruby/hash_functions.rb +54 -0
  53. data/lib/btcruby/hash_id.rb +18 -0
  54. data/lib/btcruby/key.rb +517 -0
  55. data/lib/btcruby/keychain.rb +464 -0
  56. data/lib/btcruby/network.rb +73 -0
  57. data/lib/btcruby/opcode.rb +197 -0
  58. data/lib/btcruby/open_assets/asset.rb +35 -0
  59. data/lib/btcruby/open_assets/asset_address.rb +49 -0
  60. data/lib/btcruby/open_assets/asset_definition.rb +75 -0
  61. data/lib/btcruby/open_assets/asset_id.rb +24 -0
  62. data/lib/btcruby/open_assets/asset_marker.rb +94 -0
  63. data/lib/btcruby/open_assets/asset_processor.rb +377 -0
  64. data/lib/btcruby/open_assets/asset_transaction.rb +184 -0
  65. data/lib/btcruby/open_assets/asset_transaction_builder/errors.rb +15 -0
  66. data/lib/btcruby/open_assets/asset_transaction_builder/provider.rb +32 -0
  67. data/lib/btcruby/open_assets/asset_transaction_builder/result.rb +47 -0
  68. data/lib/btcruby/open_assets/asset_transaction_builder.rb +418 -0
  69. data/lib/btcruby/open_assets/asset_transaction_input.rb +64 -0
  70. data/lib/btcruby/open_assets/asset_transaction_output.rb +140 -0
  71. data/lib/btcruby/open_assets.rb +26 -0
  72. data/lib/btcruby/openssl.rb +536 -0
  73. data/lib/btcruby/proof_of_work.rb +110 -0
  74. data/lib/btcruby/safety.rb +26 -0
  75. data/lib/btcruby/script.rb +733 -0
  76. data/lib/btcruby/signature_hashtype.rb +37 -0
  77. data/lib/btcruby/transaction.rb +511 -0
  78. data/lib/btcruby/transaction_builder/errors.rb +15 -0
  79. data/lib/btcruby/transaction_builder/provider.rb +54 -0
  80. data/lib/btcruby/transaction_builder/result.rb +73 -0
  81. data/lib/btcruby/transaction_builder/signer.rb +28 -0
  82. data/lib/btcruby/transaction_builder.rb +520 -0
  83. data/lib/btcruby/transaction_input.rb +298 -0
  84. data/lib/btcruby/transaction_outpoint.rb +30 -0
  85. data/lib/btcruby/transaction_output.rb +315 -0
  86. data/lib/btcruby/version.rb +3 -0
  87. data/lib/btcruby/wif.rb +118 -0
  88. data/lib/btcruby/wire_format.rb +362 -0
  89. data/lib/btcruby.rb +44 -2
  90. data/sample_code/creating_a_p2sh_multisig_address.rb +21 -0
  91. data/sample_code/creating_a_transaction_manually.rb +44 -0
  92. data/sample_code/generating_an_address.rb +20 -0
  93. data/sample_code/using_transaction_builder.rb +49 -0
  94. data/spec/address_spec.rb +206 -0
  95. data/spec/all.rb +6 -0
  96. data/spec/base58_spec.rb +83 -0
  97. data/spec/block_header_spec.rb +18 -0
  98. data/spec/block_spec.rb +18 -0
  99. data/spec/currency_formatter_spec.rb +46 -0
  100. data/spec/data_spec.rb +50 -0
  101. data/spec/diagnostics_spec.rb +41 -0
  102. data/spec/key_spec.rb +205 -0
  103. data/spec/keychain_spec.rb +261 -0
  104. data/spec/network_spec.rb +48 -0
  105. data/spec/open_assets/asset_address_spec.rb +33 -0
  106. data/spec/open_assets/asset_id_spec.rb +15 -0
  107. data/spec/open_assets/asset_marker_spec.rb +47 -0
  108. data/spec/open_assets/asset_processor_spec.rb +567 -0
  109. data/spec/open_assets/asset_transaction_builder_spec.rb +273 -0
  110. data/spec/open_assets/asset_transaction_spec.rb +70 -0
  111. data/spec/proof_of_work_spec.rb +53 -0
  112. data/spec/script_spec.rb +66 -0
  113. data/spec/spec_helper.rb +8 -0
  114. data/spec/transaction_builder_spec.rb +338 -0
  115. data/spec/transaction_spec.rb +162 -0
  116. data/spec/wire_format_spec.rb +283 -0
  117. metadata +141 -7
data/spec/key_spec.rb ADDED
@@ -0,0 +1,205 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe BTC::Key do
4
+
5
+ def verify_rfc6979_nonce(keyhex, msg, khex)
6
+ keybin = BTC::Data.data_from_hex(keyhex)
7
+ hash = BTC.sha256(msg)
8
+ k = BTC::OpenSSL.rfc6979_ecdsa_nonce(hash, keybin)
9
+ k.to_hex.must_equal khex
10
+ end
11
+
12
+ def verify_rfc6979_signature(keyhex, msg, sighex)
13
+ key = BTC::Key.new(private_key: keyhex.from_hex)
14
+ hash = BTC.sha256(msg)
15
+ sig = key.ecdsa_signature(hash)
16
+ sig.to_hex.must_equal sighex
17
+ end
18
+
19
+ it "should use deterministic ECDSA nonce according to RFC6979" do
20
+ verify_rfc6979_nonce("cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50",
21
+ "sample",
22
+ "2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3")
23
+ verify_rfc6979_nonce("0000000000000000000000000000000000000000000000000000000000000001",
24
+ "Satoshi Nakamoto",
25
+ "8f8a276c19f4149656b280621e358cce24f5f52542772691ee69063b74f15d15")
26
+ verify_rfc6979_nonce("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
27
+ "Satoshi Nakamoto",
28
+ "33a19b60e25fb6f4435af53a3d42d493644827367e6453928554f43e49aa6f90")
29
+ verify_rfc6979_nonce("f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181",
30
+ "Alan Turing",
31
+ "525a82b70e67874398067543fd84c83d30c175fdc45fdeee082fe13b1d7cfdf1")
32
+ verify_rfc6979_nonce("0000000000000000000000000000000000000000000000000000000000000001",
33
+ "All those moments will be lost in time, like tears in rain. Time to die...",
34
+ "38aa22d72376b4dbc472e06c3ba403ee0a394da63fc58d88686c611aba98d6b3")
35
+ verify_rfc6979_nonce("e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2",
36
+ "There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!",
37
+ "1f4b84c23a86a221d233f2521be018d9318639d5b8bbd6374a8a59232d16ad3d")
38
+ end
39
+
40
+ it "should produce deterministic ECDSA signatures Bitcoin-canonical using nonce from RFC6979" do
41
+ verify_rfc6979_signature("cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50",
42
+ "sample",
43
+ "3045022100af340daf02cc15c8d5d08d7735dfe6b98a474ed373bdb5fbecf7571be52b384202205009fb27f37034a9b24b707b7c6b79ca23ddef9e25f7282e8a797efe53a8f124")
44
+ verify_rfc6979_signature("0000000000000000000000000000000000000000000000000000000000000001",
45
+ "Satoshi Nakamoto",
46
+ "3045022100934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d802202442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5")
47
+ verify_rfc6979_signature("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
48
+ "Satoshi Nakamoto",
49
+ "3045022100fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d002206b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5")
50
+ verify_rfc6979_signature("f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181",
51
+ "Alan Turing",
52
+ "304402207063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c022058dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea")
53
+ verify_rfc6979_signature("0000000000000000000000000000000000000000000000000000000000000001",
54
+ "All those moments will be lost in time, like tears in rain. Time to die...",
55
+ "30450221008600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b0220547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21")
56
+ verify_rfc6979_signature("e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2",
57
+ "There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!",
58
+ "3045022100b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b0220279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6")
59
+ end
60
+
61
+ it "should perform Diffie-Hellman multiplication" do
62
+ alice = BTC::Key.new(private_key: "c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a".from_hex, public_key_compressed: true)
63
+ bob = BTC::Key.new(private_key: "2db963f0fe106f483d9afa73bd4e39a8ac4bbcb1fbec99d65bf59d85c8cb62ee".from_hex, public_key_compressed: true)
64
+ dh_pubkey1 = alice.diffie_hellman(bob)
65
+ dh_pubkey1.compressed_public_key.to_hex.must_equal "03735932754bc16e10febe40ee0280906d29459d477442f1838dcf27de3b5d9699"
66
+
67
+ dh_pubkey2 = bob.diffie_hellman(alice)
68
+ dh_pubkey2.compressed_public_key.to_hex.must_equal "03735932754bc16e10febe40ee0280906d29459d477442f1838dcf27de3b5d9699"
69
+ end
70
+
71
+ it "should support compressed public keys" do
72
+ k = BTC::Key.new(private_key: "c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a".from_hex, public_key_compressed: true)
73
+ k.private_key.to_hex.must_equal "c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a"
74
+ k.public_key.to_hex.must_equal "0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71"
75
+ k.address.to_s.must_equal "1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8"
76
+ k.public_key_compressed.must_equal true
77
+
78
+ k.compressed_public_key.to_hex.must_equal "0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71"
79
+ k.uncompressed_public_key.to_hex.must_equal "0478d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71a1518063243acd4dfe96b66e3f2ec8013c8e072cd09b3834a19f81f659cc3455"
80
+
81
+ (k.compressed_key == k).must_equal true
82
+ (k.uncompressed_key == k).must_equal false
83
+
84
+ k.compressed_key.address.to_s.must_equal "1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8"
85
+ k.uncompressed_key.address.to_s.must_equal "1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"
86
+ end
87
+
88
+ it "should support uncompressed public keys" do
89
+ k = BTC::Key.new(private_key: BTC::Data.data_from_hex("c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a"), public_key_compressed: false)
90
+ k.private_key.to_hex.must_equal "c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a"
91
+ k.public_key.to_hex.must_equal "0478d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71a1518063243acd4dfe96b66e3f2ec8013c8e072cd09b3834a19f81f659cc3455"
92
+ k.address.to_s.must_equal "1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"
93
+ k.public_key_compressed.must_equal false
94
+
95
+ k.compressed_public_key.to_hex.must_equal "0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71"
96
+ k.uncompressed_public_key.to_hex.must_equal "0478d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71a1518063243acd4dfe96b66e3f2ec8013c8e072cd09b3834a19f81f659cc3455"
97
+
98
+ (k.compressed_key == k).must_equal false
99
+ (k.uncompressed_key == k).must_equal true
100
+
101
+ k.compressed_key.address.to_s.must_equal "1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8"
102
+ k.uncompressed_key.address.to_s.must_equal "1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"
103
+ end
104
+
105
+ it "should sign and verify data using ECDSA signatures" do
106
+ 300.times do |n|
107
+ message = "Test message #{n}"
108
+ private_key = "Key #{n}".sha256
109
+
110
+ key = BTC::Key.new(private_key: private_key)
111
+
112
+ signature = key.ecdsa_signature(message.sha256)
113
+
114
+ signature.bytesize.must_be :>=, 60
115
+ signature.bytesize.must_be :<=, 72
116
+
117
+ key2 = BTC::Key.new(public_key: key.public_key)
118
+
119
+ result = key2.verify_ecdsa_signature(signature, message.sha256)
120
+ result.must_equal true
121
+ end
122
+ end
123
+
124
+ it "should test canonicality of the signature" do
125
+
126
+ # Stress-test canonicality checks
127
+ key = BTC::Key.new(private_key: BTC.sha256("some key"))
128
+ 2560.times do |i|
129
+ hash = BTC.sha256("tx#{i}")
130
+ sig = key.ecdsa_signature(hash) + "\x01".b
131
+ canonical = Key.validate_script_signature(sig)
132
+ if !canonical
133
+ puts Diagnostics.current.last_message
134
+ end
135
+ canonical.must_equal true
136
+ end
137
+
138
+ sig = "3045022100e81a33ac22d0ef25d359a5353977f0f953608b2733141239ec02363237ab6781022045c71237e95b56079e9fa88591060e4c1a4bb02c0cad1ebeb092749d4aa9754701".from_hex
139
+ canonical = Key.validate_script_signature(sig)
140
+ if !canonical
141
+ puts Diagnostics.current.last_message
142
+ end
143
+ canonical.must_equal true
144
+ end
145
+
146
+ it "should test normalization of non-canonical signatures" do
147
+
148
+ # # Generate non-canonical signatures
149
+ # key = BTC::Key.new(private_key: BTC.sha256("some other key"))
150
+ # 2560.times do |i|
151
+ # hash = BTC.sha256("tx#{i}")
152
+ # sig = key.ecdsa_signature(hash, normalized: false) + "\x01".b
153
+ # if !Key.validate_script_signature(sig)
154
+ # puts sig[0, sig.size - 1].to_hex
155
+ # end
156
+ # end
157
+ %w[
158
+ 30460221009bf6f0f7480d0562f5f56bc53bc90d564d7ce15ecbd116716c583da7311098b8022100880fd3e1c788c144aeede40072e814bead8b470a5a7f978a3804363b4920dc37
159
+ 304502206700757f4609501016944a7b3144fee587848ad108c79dd4eb7664ac374d646c022100b762e1010f4300fb3b15cde575c80ad405314472812051e34823a5051bfc13fe
160
+ 3046022100e71c70039b4f3c3894a3e5d20d1e19f95fc7b216a3ad2f540bd817470c07530e022100ebacde4827011195bd789edb6e3bca4dd0d5b59cae6892ee88465ebb205674b3
161
+ 3045022000ef0203c4df709ab28f701a6c340ecce0c30d60b717096c7a360d4a801371fe022100cd83e86d6d5f0304d8ef428ed928a5af3fcfec652fb334bbfb9e2750c8ebb51a
162
+ 3046022100cda613be5da7afc13aa6e04399557603b10317ca09e53a8d24c4ea0d892a0a9702210081a3012d810d6228c532fc8225dc32babda8f7b7f749c656b84af14e41a50ef2
163
+ 3045022063ec43b0a6515abe17636058aac61b11b2e11becbb49439b711cb868788fe52e022100ed350ebafa430efc8e55285aa94ee8365345238596412ac085f9fc0898bb0003
164
+ 3046022100d4890791c4b9f9fc7a9e853da404df26ab2302c5046527faaf9ae9fd8c99213a022100daec31bda7aa8ce0d9459e14d9fe86feda806ff90b6dd9701a6d94fad04a2860
165
+ 304502203ee680db8f247e005e84bb0db8e0a39bf47d32282c730b0f1b003c1f32891730022100d69b240494d0562de75b3916acc023711cc37c30d70760ab1fc35e8931738410
166
+ 304502206f08b37b53fd27096161015c53e20e05e01af59a32333e2bb79fb629479bd3800221009b125d988cd210c2438e6a206f5b9ddd7b6eae67accb0b914fde6b2a80c39b21
167
+ 3046022100bf94dadbff4a67716955fcc4e22c3c4c88852ef31c5a0c1145ad3d433c97705d022100f2c339e6a1cc6ce75fda29b9d47dc2e7f6fb1df4a2f5e8502a75faf63f2cc87b
168
+ 3046022100cf268bfa6c9b0a10747979654e386fb64d18bd2ae69e549a0545dc2fe154a6cb022100f651c4389233482f8b99f39f2e2d1fd29c08795f0dad50de47860bca29a4300d
169
+ 304502203a969e1650b758a6597c01beb050ae737838c24376fcf0cf8fb66b2bb1b291a7022100b59ba04b19a00becb4e41e964cec1c29fd5d080be53d844b020d613a71ae7f65
170
+ 304602210085234fe98f3bb43e665b2d138de16a265751b83851b1fc50478359c731b9a185022100db2a37784987554344071e60df361d847e1ee2c18844321a7a1edb9a0b53cd92
171
+ 3046022100b07a37a66c5db8c718147d16b74fbba048e632911be0db380821c399e4823702022100ff67919de148346fdb4d25676d4c4e42a5002002c9c7e20176783ceb46e08a02
172
+ 3046022100a962f13335383fa0a2ad2c38568fe87301dcc7b8e78cafc05dc3e805c0ecb02b022100c15a38847cc259b0f616cb477ecde0ea0fe8767f0aac1e630e314fcde4c65487
173
+ 3046022100a8ba33f415f456c7f8daac01824a17612927c4c2b35876caf26b63e33fa17b99022100b4e89ea2021afd554f5f048cc5b9208bba55e727133ca184b51de121654efb33
174
+ 3045022079b58a6574851d07ab8779a6a6597c326f9181852f88156546ce2331f95bc5b2022100dea5a3b8f454a781e444854416b2a42bacc02d1d21ad385022f50b439c98c076
175
+ 304502201ff1e2b23342bc5d2960822247084fb86e4be9a37bb0a6a9aad512b6627c2e0a0221008624ceebb150e1de1103b51e1ac533f43e8e7322a37c925baae0dcc90e272ea2
176
+ 304502205ee578855612719d0f4a7bcaf149d5f283a12176e99a27042d9d8f4a9074f2dd022100f4d7b7b923de4d63e646bb6890ef3634bc317c6dd04e55f2ffa686e32574ccb7
177
+ 3046022100c2e4f75fd6c294c03dd684608212ec65240bd3319abe20663c7a24676ad8ef600221008743b32fd6051c2eaf326f2e824d3a8ca0ebdc1aa25175ee6b61c1f694b9cfbd
178
+ 30450220450d1e68977e2ad2776925725e444eb9a58743459a4fd15188552cfc171f8798022100edac0b9210f02f0d8d4201729c8d62c7c302cb7c1a6f9f2f7741c7f99a3518a2
179
+ 3045022018cddb05aa87011d7fe3ba56d6657bd813eeab9c43e2422af2f44cdd3bd8bc3a022100c53eb96ba81e12e59da73846223310ea6cb325a42ab2a081a101d99c5e3bfeec
180
+ 304502200be9a279cca265b4e273c165d877474ab854b14ce2b17dff958dc932eae3d9e8022100e45c9087e78ff0120ae2f911299f64c995c61d6750a5913eaac36f096b560685
181
+ 3045022062d08bff9580238c8cd62d9d64e9c1d518932886651f46a445bff773993500f00221008029263cc9ec64589bbbe140995096c257a1fbacf5f9f5ee69a69b96c626cc7a
182
+ ].each do |hex_sig|
183
+ sig = hex_sig.from_hex
184
+ canonical = Key.validate_script_signature(sig + "\x01".b)
185
+ canonical.must_equal false
186
+ sig2 = Key.normalized_signature(sig)
187
+ sig2.wont_equal nil
188
+ sig2.wont_equal sig
189
+ canonical = Key.validate_script_signature(sig2 + "\x01".b)
190
+ if !canonical
191
+ puts Diagnostics.current.last_message
192
+ end
193
+ canonical.must_equal true
194
+
195
+ # Non-canonical signature must be normalized
196
+ sig3 = Key.validate_and_normalize_script_signature(sig + "\x01".b)
197
+ sig3.must_equal sig2 + "\x01".b
198
+
199
+ # Canonical signature should stay the same
200
+ sig4 = Key.validate_and_normalize_script_signature(sig2 + "\x01".b)
201
+ sig4.must_equal sig2 + "\x01".b
202
+ end
203
+ end
204
+
205
+ end
@@ -0,0 +1,261 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe BTC::Keychain do
4
+
5
+ it "should support tpub/tprv prefixes for testnet" do
6
+ seed = "000102030405060708090a0b0c0d0e0f".from_hex
7
+ master = Keychain.new(seed: seed)
8
+ master.network = Network.testnet
9
+ master.xpub.must_equal "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp"
10
+ master.xprv.must_equal "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m"
11
+ master.network = Network.mainnet
12
+ master.xpub.must_equal "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
13
+ master.xprv.must_equal "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
14
+ end
15
+
16
+ it "should support path API" do
17
+ seed = "000102030405060708090a0b0c0d0e0f".from_hex
18
+ master = Keychain.new(seed: seed)
19
+ master.derived_keychain("").xpub.must_equal "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
20
+ master.derived_keychain("m").xpub.must_equal "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
21
+ master.derived_keychain("0'").xpub.must_equal "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"
22
+ master.derived_keychain("m/0'").xpub.must_equal "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"
23
+ master.derived_keychain("0'").xpub.must_equal "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"
24
+ master.derived_keychain("m/0'/1").xpub.must_equal "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"
25
+ master.derived_keychain("0'/1").xpub.must_equal "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"
26
+ master.derived_keychain("m/0'/1/2'").xprv.must_equal "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM"
27
+ master.derived_keychain("0'/1/2'").xprv.must_equal "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM"
28
+ master.derived_keychain("m/0'/1/2'/2/1000000000").xpub.must_equal "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"
29
+ master.derived_keychain("0'/1/2'/2/1000000000").xprv.must_equal "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76"
30
+ end
31
+
32
+ it "should support test vector 1" do
33
+ # Master (hex): 000102030405060708090a0b0c0d0e0f
34
+ # * [Chain m]
35
+ # * ext pub: xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8
36
+ # * ext prv: xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi
37
+ # * [Chain m/0']
38
+ # * ext pub: xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw
39
+ # * ext prv: xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7
40
+ # * [Chain m/0'/1]
41
+ # * ext pub: xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ
42
+ # * ext prv: xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
43
+ # * [Chain m/0'/1/2']
44
+ # * ext pub: xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5
45
+ # * ext prv: xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM
46
+ # * [Chain m/0'/1/2'/2]
47
+ # * ext pub: xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV
48
+ # * ext prv: xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334
49
+ # * [Chain m/0'/1/2'/2/1000000000]
50
+ # * ext pub: xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy
51
+ # * ext prv: xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76
52
+
53
+ seed = "000102030405060708090a0b0c0d0e0f".from_hex
54
+ master = Keychain.new(seed: seed)
55
+
56
+ master.key.address.to_s.must_equal "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma"
57
+ master.key.address.to_s.must_equal master.public_keychain.key.address.to_s
58
+
59
+ master.parent_fingerprint.must_equal 0
60
+ master.identifier.to_hex.must_equal "3442193e1bb70916e914552172cd4e2dbc9df811"
61
+ master.fingerprint.must_equal 876747070
62
+ master.depth.must_equal 0
63
+ master.index.must_equal 0
64
+ master.hardened?.must_equal false
65
+ master.mainnet?.must_equal true
66
+ master.testnet?.must_equal false
67
+ master.private?.must_equal true
68
+ master.hardened?.must_equal false
69
+
70
+ master.xpub.must_equal "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
71
+ master.xprv.must_equal "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
72
+
73
+ master2 = Keychain.new(xprv: master.extended_private_key)
74
+ master2.private?.must_equal true
75
+ (master2 == master).must_equal true
76
+ master2.xpub.must_equal master.xpub
77
+ master2.xprv.must_equal master.xprv
78
+
79
+ m0prv = master.derived_keychain(0, hardened:true)
80
+
81
+ m0prv.parent_fingerprint.wont_equal 0
82
+ m0prv.depth.must_equal 1
83
+ m0prv.index.must_equal 0
84
+ m0prv.private?.must_equal true
85
+ m0prv.hardened?.must_equal true
86
+
87
+ m0prv.extended_public_key.must_equal "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"
88
+ m0prv.extended_private_key.must_equal "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7"
89
+
90
+ m0prv1pub = m0prv.derived_keychain(1)
91
+
92
+ m0prv1pub.parent_fingerprint.wont_equal 0
93
+ m0prv1pub.depth.must_equal 2
94
+ m0prv1pub.index.must_equal 1
95
+ m0prv1pub.private?.must_equal true
96
+ m0prv1pub.hardened?.must_equal false
97
+
98
+ m0prv1pub.extended_public_key.must_equal "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"
99
+ m0prv1pub.extended_private_key.must_equal "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs"
100
+
101
+ m0prv1pub2prv = m0prv1pub.derived_keychain(2, hardened:true)
102
+ m0prv1pub2prv.extended_public_key.must_equal "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5"
103
+ m0prv1pub2prv.extended_private_key.must_equal "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM"
104
+
105
+ m0prv1pub2prv2pub = m0prv1pub2prv.derived_keychain(2)
106
+ m0prv1pub2prv2pub.extended_public_key.must_equal "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV"
107
+ m0prv1pub2prv2pub.extended_private_key.must_equal "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334"
108
+
109
+ m0prv1pub2prv2pub1Gpub = m0prv1pub2prv2pub.derived_keychain(1000000000)
110
+ m0prv1pub2prv2pub1Gpub.extended_public_key.must_equal "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"
111
+ m0prv1pub2prv2pub1Gpub.extended_private_key.must_equal "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76"
112
+
113
+
114
+ end
115
+
116
+ it "should support test vector 2" do
117
+ # Master (hex): fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542
118
+ # * [Chain m]
119
+ # * ext pub: xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB
120
+ # * ext prv: xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U
121
+ # * [Chain m/0]
122
+ # * ext pub: xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH
123
+ # * ext prv: xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt
124
+ # * [Chain m/0/2147483647']
125
+ # * ext pub: xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a
126
+ # * ext prv: xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9
127
+ # * [Chain m/0/2147483647'/1]
128
+ # * ext pub: xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon
129
+ # * ext prv: xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef
130
+ # * [Chain m/0/2147483647'/1/2147483646']
131
+ # * ext pub: xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL
132
+ # * ext prv: xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc
133
+ # * [Chain m/0/2147483647'/1/2147483646'/2]
134
+ # * ext pub: xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt
135
+ # * ext prv: xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j
136
+
137
+ seed = "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542".from_hex
138
+ master = Keychain.new(seed: seed)
139
+
140
+ master.parent_fingerprint.must_equal 0
141
+ master.identifier.to_hex.must_equal "bd16bee53961a47d6ad888e29545434a89bdfe95"
142
+ master.fingerprint.must_equal 3172384485
143
+ master.depth.must_equal 0
144
+ master.index.must_equal 0
145
+ master.hardened?.must_equal false
146
+ master.mainnet?.must_equal true
147
+ master.testnet?.must_equal false
148
+ master.private?.must_equal true
149
+ master.hardened?.must_equal false
150
+
151
+ master.extended_public_key.must_equal "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"
152
+ master.extended_private_key.must_equal "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U"
153
+
154
+ m0pub = master.derived_keychain(0)
155
+ m0pub.extended_public_key.must_equal "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"
156
+ m0pub.extended_private_key.must_equal "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt"
157
+
158
+ m0pubFFprv = m0pub.derived_keychain(2147483647, hardened:true)
159
+ m0pubFFprv.extended_public_key.must_equal "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a"
160
+ m0pubFFprv.extended_private_key.must_equal "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9"
161
+
162
+ m0pubFFprv1 = m0pubFFprv.derived_keychain(1)
163
+ m0pubFFprv1.extended_public_key.must_equal "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon"
164
+ m0pubFFprv1.extended_private_key.must_equal "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef"
165
+
166
+ m0pubFFprv1pubFEprv = m0pubFFprv1.derived_keychain(2147483646, hardened:true)
167
+ m0pubFFprv1pubFEprv.extended_public_key.must_equal "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"
168
+ m0pubFFprv1pubFEprv.extended_private_key.must_equal "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc"
169
+
170
+ m0pubFFprv1pubFEprv2 = m0pubFFprv1pubFEprv.derived_keychain(2)
171
+ m0pubFFprv1pubFEprv2.extended_public_key.must_equal "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"
172
+ m0pubFFprv1pubFEprv2.extended_private_key.must_equal "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j"
173
+ end
174
+
175
+ it "should behave correctly on privkeys below 32-byte size" do
176
+ keychain = Keychain.new(seed: "stress test")
177
+ #puts keychain.extended_private_key
178
+ # Uncomment this to figure out the indexes for the shorter keys
179
+ if false
180
+ indexes = []
181
+ 10000.times do |i|
182
+ key = keychain.derived_key(i, hardened: true)
183
+ key.private_key.bytesize.must_equal 32
184
+ key.public_key.bytesize.must_equal 33
185
+ if key.private_key.bytes[0] == 0
186
+ indexes << i
187
+ puts "i = #{i} " + key.private_key.to_hex + " #{key.address.to_s}"
188
+ end
189
+ end
190
+ puts "Short private key indexes: #{indexes.inspect}"
191
+ end
192
+
193
+ # These indexes are brute-forced in the block above.
194
+ [70, 227, 455, 524, 530, 583,
195
+ 1150, 1193, 1351, 1987,
196
+ 2209, 2320, 2703, 2800, 2984,
197
+ 3029, 3203, 3275, 3472, 3526, 3896, 3900,
198
+ 4070, 4236, 4670, 4831, 4929,
199
+ 5233, 5254, 5301, 5609, 5980,
200
+ 6202, 6283, 6313, 6430, 6951,
201
+ 7056, 7060, 7211, 7274, 7311, 7614, 7897,
202
+ 8313, 8328, 8329, 8840, 8950, 8996,
203
+ 9323, 9354].each do |i|
204
+ key = keychain.derived_key(i, hardened: true)
205
+ key.private_key.bytesize.must_equal 32
206
+ key.public_key.bytesize.must_equal 33
207
+ key.private_key.bytes[0].must_equal 0
208
+ key2 = keychain.derived_key(i, hardened: false)
209
+ key2.private_key.bytesize.must_equal 32
210
+ key2.public_key.bytesize.must_equal 33
211
+ end
212
+
213
+ # same as BIP32.org and CoreBitcoin
214
+ keychain.derived_key(70, hardened: true).address.to_s.must_equal '1FZQfsXwAoUcn9WVwbfRb4jMMkPJEozLWH'
215
+ keychain.derived_key(70, hardened: true).private_key.bytes[0].must_equal 0x00
216
+ keychain.derived_key(227, hardened: true).address.to_s.must_equal '1LRbeWJC3sLGRk7ob82djVYTNhsH2UdR4f'
217
+ keychain.derived_key(227, hardened: true).private_key.bytes[0].must_equal 0x00
218
+ keychain.derived_key(455, hardened: true).address.to_s.must_equal '1HSr4B5Hr3hc7vAzNHbp7SV7rsFzUhQSeF'
219
+ keychain.derived_key(455, hardened: true).private_key.bytes[0].must_equal 0x00
220
+ end
221
+
222
+ it "should verify a certain regression test" do
223
+ extprv = "xprv9s21ZrQH143K3ZhiFsU612wiYCnd5miCTnWRMRJCmbTUxnn3F2WXuTXcoEyWpsit8ZqS5ddNvsoaEQuwzNwH8nmVDS24NwHbiu5oCrj85Kz"
224
+ keychain = Keychain.new(xprv: extprv)
225
+ key0 = keychain.derived_keychain(0, hardened: false).key
226
+ # puts "UNCOMPR: #{key0.uncompressed_key.address.to_s}"
227
+ # puts " COMPR: #{key0.compressed_key.address.to_s}"
228
+ # puts "DEFAULT: #{key0.address.to_s}"
229
+ # Test reports: 1MLjpNJZ3KZUdd5J9ZVnhxjFioC8DnhSr4
230
+ # BIP32.org reports: 15aALBTZkDrW8iZBKXrUHQo9dPJtGPEHSy
231
+ key0.address.to_s.must_equal "15aALBTZkDrW8iZBKXrUHQo9dPJtGPEHSy"
232
+ end
233
+
234
+ it "should support conversion to public keychain" do
235
+ seed = "000102030405060708090a0b0c0d0e0f".from_hex
236
+ master = Keychain.new(seed: seed)
237
+
238
+ m0prv = master.derived_keychain(0, hardened:true)
239
+ m0prv_pub = m0prv.public_keychain
240
+
241
+ m0prv.private?.must_equal true
242
+ m0prv.public?.must_equal false
243
+ m0prv.hardened?.must_equal true
244
+
245
+ m0prv_pub.extended_public_key.must_equal m0prv.extended_public_key
246
+ m0prv_pub.extended_private_key.must_equal nil
247
+ m0prv_pub.private?.must_equal false
248
+ m0prv_pub.public?.must_equal true
249
+ m0prv_pub.hardened?.must_equal true
250
+
251
+ m0prv_pub2 = Keychain.new(extended_key: m0prv.extended_public_key)
252
+ m0prv_pub2.must_equal m0prv_pub
253
+ end
254
+
255
+ it "should support public-only derivation" do
256
+ keychain = Keychain.new(xpub: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB")
257
+ m0pub = keychain.derived_keychain(0)
258
+ m0pub.extended_public_key.must_equal "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"
259
+ m0pub.extended_private_key.must_equal nil
260
+ end
261
+ end
@@ -0,0 +1,48 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe BTC::Network do
4
+
5
+ it "should have mainnet shared instance" do
6
+ mainnet = BTC::Network.mainnet
7
+ mainnet.name.must_equal "mainnet"
8
+ mainnet.mainnet?.must_equal true
9
+ mainnet.mainnet.must_equal true
10
+ mainnet.testnet?.must_equal false
11
+ mainnet.testnet.must_equal false
12
+ mainnet.genesis_block.block_id.must_equal "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
13
+ mainnet.genesis_block_header.block_id.must_equal "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
14
+ mainnet.max_target.must_equal 0x00000000ffff0000000000000000000000000000000000000000000000000000
15
+ BTC::Network.mainnet.object_id.must_equal BTC::Network.mainnet.object_id
16
+ end
17
+
18
+ it "should have testnet shared instance" do
19
+ testnet = BTC::Network.testnet
20
+ testnet.name.must_equal "testnet3"
21
+ testnet.mainnet?.must_equal false
22
+ testnet.mainnet.must_equal false
23
+ testnet.testnet?.must_equal true
24
+ testnet.testnet.must_equal true
25
+ testnet.genesis_block.block_id.must_equal "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
26
+ testnet.genesis_block_header.block_id.must_equal "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
27
+ testnet.max_target.must_equal 0x00000007fff80000000000000000000000000000000000000000000000000000
28
+ BTC::Network.testnet.object_id.must_equal BTC::Network.testnet.object_id
29
+ end
30
+
31
+ it "should allow copying" do
32
+ network = BTC::Network.testnet.dup
33
+ network.object_id.wont_equal BTC::Network.testnet.object_id
34
+
35
+ network.name.must_equal "testnet3"
36
+ network.mainnet?.must_equal false
37
+ network.testnet?.must_equal true
38
+
39
+ network.name = "xnetwork"
40
+ network.mainnet = true
41
+ network.mainnet?.must_equal true
42
+ network.testnet?.must_equal false
43
+
44
+ BTC::Network.testnet.mainnet?.must_equal false
45
+ BTC::Network.testnet.testnet?.must_equal true
46
+ end
47
+
48
+ end
@@ -0,0 +1,33 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe BTC::AssetAddress do
4
+ it "should encode bitcoin address to a correct asset address" do
5
+ btc_address = Address.parse("16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM")
6
+ asset_address = AssetAddress.new(bitcoin_address: btc_address)
7
+ asset_address.bitcoin_address.must_equal btc_address
8
+ asset_address.to_s.must_equal "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"
9
+ asset_address.mainnet?.must_equal true
10
+ end
11
+ it "should decode an asset address" do
12
+ asset_address = Address.parse("akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy")
13
+ asset_address.bitcoin_address.to_s.must_equal "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM"
14
+ asset_address.mainnet?.must_equal true
15
+ end
16
+ it "should allow instantiating Asset Address with an Asset Address" do
17
+ asset_address = AssetAddress.new(bitcoin_address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy")
18
+ asset_address.to_s.must_equal "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"
19
+ asset_address.bitcoin_address.to_s.must_equal "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM"
20
+ asset_address.mainnet?.must_equal true
21
+ end
22
+ it "should support P2SH address for assets" do
23
+ asset_address = AssetAddress.new(bitcoin_address: "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX")
24
+ asset_address.to_s.must_equal "anQin2TDYaubr6M5MQM8kNXMitHc2hsmfGc"
25
+ asset_address.bitcoin_address.to_s.must_equal "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX"
26
+ asset_address.mainnet?.must_equal true
27
+
28
+ asset_address = Address.parse("anQin2TDYaubr6M5MQM8kNXMitHc2hsmfGc")
29
+ asset_address.class.must_equal(AssetAddress)
30
+ asset_address.bitcoin_address.to_s.must_equal "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX"
31
+ asset_address.mainnet?.must_equal true
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe BTC::AssetID do
4
+ it "should encode script to a correct address" do
5
+ key = Key.new(private_key: "18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725".from_hex, public_key_compressed: false)
6
+ asset_id = AssetID.new(script: key.address(network: Network.mainnet).script, network: Network.mainnet)
7
+ asset_id.to_s.must_equal "ALn3aK1fSuG27N96UGYB1kUYUpGKRhBuBC"
8
+ asset_id.is_a?(BTC::AssetID).must_equal true
9
+ end
10
+ it "should decode an asset address" do
11
+ asset_id = Address.parse("ALn3aK1fSuG27N96UGYB1kUYUpGKRhBuBC")
12
+ asset_id.is_a?(BTC::AssetID).must_equal true
13
+ asset_id.hash.must_equal "36e0ea8e93eaa0285d641305f4c81e563aa570a2".from_hex
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe BTC::AssetMarker do
4
+ it "should decode marker output" do
5
+ # Data in the marker output Description
6
+ # ----------------------------- -------------------------------------------------------------------
7
+ # 0x6a The OP_RETURN opcode.
8
+ # 0x11 The PUSHDATA opcode for a 17 bytes payload.
9
+ # 0x4f 0x41 The Open Assets Protocol tag.
10
+ # 0x01 0x00 Version 1 of the protocol.
11
+ # 0x03 There are 3 items in the asset quantity list.
12
+ # 0xac 0x02 0x00 0xe5 0x8e 0x26 The asset quantity list:
13
+ # - '0xac 0x02' means output 0 has an asset quantity of 300.
14
+ # - Output 1 is skipped and has an asset quantity of 0
15
+ # because it is the marker output.
16
+ # - '0x00' means output 2 has an asset quantity of 0.
17
+ # - '0xe5 0x8e 0x26' means output 3 has an asset quantity of 624,485.
18
+ # - Outputs after output 3 (if any) have an asset quantity of 0.
19
+ # 0x05 The metadata is 5 bytes long.
20
+ # 0x48 0x65 0x6c 0x6c 0x6f Some arbitrary metadata.
21
+ output = TransactionOutput.new(value: 0, script: Script.new << OP_RETURN << "4f41010003ac0200e58e260548656c6c6f".from_hex)
22
+ marker = AssetMarker.new(output: output)
23
+ marker.quantities.must_equal [300, 0, 624_485]
24
+ marker.metadata.must_equal "Hello"
25
+
26
+ marker = AssetMarker.new(script: output.script)
27
+ marker.quantities.must_equal [300, 0, 624_485]
28
+ marker.metadata.must_equal "Hello"
29
+
30
+ marker = AssetMarker.new(data: output.script.op_return_data)
31
+ marker.quantities.must_equal [300, 0, 624_485]
32
+ marker.metadata.must_equal "Hello"
33
+ end
34
+
35
+ it "should encode marker output" do
36
+ marker = AssetMarker.new
37
+ marker.data.must_equal "4f4101000000".from_hex
38
+ marker.metadata = "Hello"
39
+ marker.data.must_equal "4f410100000548656c6c6f".from_hex
40
+ marker.quantities = [1]
41
+ marker.data.must_equal "4f41010001010548656c6c6f".from_hex
42
+ marker.quantities = [1, 2]
43
+ marker.data.must_equal "4f4101000201020548656c6c6f".from_hex
44
+ marker.quantities = [300, 0, 624_485]
45
+ marker.data.must_equal "4f41010003ac0200e58e260548656c6c6f".from_hex
46
+ end
47
+ end