-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathbitcoinolog.pl
209 lines (168 loc) · 8.09 KB
/
bitcoinolog.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Bitcoinolog: Reason about Bitcoin addresses with Prolog.
Written 2017-2024 by Markus Triska (triska@metalevel.at)
For more information, visit:
https://www.metalevel.at/bitcoinolog/
=====================================
Tested with Scryer Prolog.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
:- module(bitcoinolog, [new_private_key/1,
private_key_to_public_key/2,
private_key_to_wif/2,
public_key_to_address/2,
hex_to_base58check/2,
base58check_to_integer/2
]).
:- use_module(library(clpz)).
:- use_module(library(crypto)).
:- use_module(library(dcgs)).
:- use_module(library(error)).
:- use_module(library(format)).
:- use_module(library(lists)).
:- use_module(library(between)).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Sample use: Offline generation of Bitcoin addresses.
?- repeat,
new_private_key(PrivateKey),
private_key_to_public_key(PrivateKey, PublicKey),
public_key_to_address(PublicKey, Address),
format:portray_clause(Address),
false.
"142zbV8APtULexQai6NtrcSZxV9MoPwPPR".
"13wh2Bhhc3B7M2k1ybWtSywBftrQ7Wm2wY".
"1JxcrPifBFTUjZzYeFuPsWoaaZd5VR9yc".
"12GDcpqLvgJiyPJRMqDFoZXvmx5LTn9uaK".
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
bitcoin_curve(Curve) :-
crypto_name_curve(secp256k1, Curve).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Generate a cryptographically secure random integer between 0 and 2^256.
Almost all such integers can be used as *private keys* for Bitcoin.
If an integer outside the suitable range is generated, an exception
is thrown. The chances of this happening are extremely low.
Sample use:
?- new_private_key(K).
%@ K = 39615274996621223576805938914018474719127698794300917857701300837868839491832.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
new_private_key(Key) :-
crypto_n_random_bytes(32, Bytes),
hex_bytes(Hex, Bytes),
hex_to_integer(Hex, Key),
bitcoin_curve(Curve),
crypto_curve_order(Curve, Order),
Upper #= Order - 1,
( between(1, Upper, Key) -> true
; domain_error(private_key, Key, new_private_key/1)
).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
A public key is a point on the curve, with coordinates (X,Y). In
ECDSA, the public key can be derived from the private key by
multiplying the generator with the private key.
Deriving a public key and address is described in:
https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
We deviate from this description in step (2) in that we store
public keys in *compressed* form. This means that we use Prefix++X,
where Prefix is either 0x02 or 0x03 depending on whether Y is even
or odd, respectively.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
private_key_to_public_key(PrivateKey, PublicKey) :-
bitcoin_curve(Curve),
crypto_curve_generator(Curve, Generator),
% 1. Compute the public key as PrivateKey*Generator.
crypto_curve_scalar_mult(Curve, PrivateKey, Generator, point(X,Y)),
% 2. PublicKey in compressed form.
Rem #= Y mod 2,
zcompare(Cmp, 0, Rem),
cmp0_prefix(Cmp, Prefix),
phrase(format_("~s~|~`0t~16r~64+", [Prefix,X]), PublicKey).
cmp0_prefix(=, "02").
cmp0_prefix(<, "03").
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Convert a private key to Wallet Import Format (WIF).
This is how private keys are typically shown in wallets.
The prefix 80 is used for private keys. 01 is appended to indicate
that these private keys correspond to *compressed* public keys.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
private_key_to_wif(PrivateKey0, WIF) :-
phrase(format_("80~|~`0t~16r~64+01", [PrivateKey0]), PrivateKey),
% Twice perform SHA-256 on the hex encoding of the private key.
hex_algorithm_hash(PrivateKey, sha256, HashPrivateKey1),
hex_algorithm_hash(HashPrivateKey1, sha256, HashPrivateKey2),
% Take the first four bytes, which are the checksum.
hex_bytes(HashPrivateKey2, Bytes),
Bytes = [B1,B2,B3,B4|_],
hex_bytes(PrivateKey, PrivateKeyBytes),
append(PrivateKeyBytes, [B1,B2,B3,B4], WIF0),
% Convert the address to Base58Check encoding.
hex_bytes(WIF1, WIF0),
hex_to_base58check(WIF1, WIF).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Convert a public key to a Bitcoin address in Base58Check encoding.
This encoding is commonly shown for Bitcoin addresses.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
public_key_to_address(PublicKey, Address) :-
% 1. Compute public key from private key: already done!
% 2. Perform SHA-256 on the hex encoding of the public key.
hex_algorithm_hash(PublicKey, sha256, HashPublicKey),
% 3. Perform RIPEMD-160 hashing on the result.
hex_algorithm_hash(HashPublicKey, ripemd160, RIPEMD160),
% 4. Add version byte in front of RIPEMD-160.
% 0x00 denotes Main Network.
append("00", RIPEMD160, ExtendedRIPE),
% 5. Perform SHA-256 on the extended RIPEMD-160 result.
hex_algorithm_hash(ExtendedRIPE, sha256, SHA256_1),
% 6. Perform SHA-256 on the previous result.
hex_algorithm_hash(SHA256_1, sha256, SHA256_2),
% 7. Take the first four bytes, which are the checksum.
hex_bytes(SHA256_2, Bytes),
Bytes = [B1,B2,B3,B4|_],
% 8. Add the 4 checksum bytes at the end of extended RIPEMD-160.
% This is the 25-byte binary Bitcoin Address.
hex_bytes(ExtendedRIPE, ExtendedBytes),
append(ExtendedBytes, [B1,B2,B3,B4], Address0),
% 9. Convert the address to Base58Check encoding.
hex_bytes(Address1, Address0),
hex_to_base58check(Address1, Address).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Compute the hash of a hex code, using Algorithm.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
hex_algorithm_hash(Hex, Algorithm, Hash) :-
hex_bytes(Hex, Bytes),
maplist(char_code, Chars, Bytes),
crypto_data_hash(Chars, Hash, [algorithm(Algorithm),
encoding(octet)]).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Base58Check format.
The most important conversion predicate is hex_to_base58check/2.
The predicate base58check_to_integer/2 can be useful too.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
base58_alphabet("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz").
hex_to_base58check(Hex, Base58Check) :-
hex_to_integer(Hex, Integer),
phrase(base58check(Integer), Bs0),
reverse(Bs0, Bs),
hex_bytes(Hex, Bytes),
once(phrase(leading_zeroes(Bytes), Base58Check, Bs)).
leading_zeroes([]) --> [].
leading_zeroes([0|Rest]) --> "1", leading_zeroes(Rest).
leading_zeroes([D|_]) --> { D #\= 0 }.
base58check(I0) -->
{ zcompare(C, 0, I0) },
base58check_(C, I0).
base58check_(=, _) --> [].
base58check_(<, I0) --> [Char],
{ I #= I0 // 58,
Remainder #= I0 mod 58,
base58_alphabet(As),
nth0(Remainder, As, Char) },
base58check(I).
base58check_to_integer(B58, Integer) :-
foldl(base58_times58, B58, 0, Integer).
base58_times58(Char, S0, S) :-
base58_alphabet(As),
nth0(Value, As, Char),
S #= S0*58 + Value.
hex_to_integer(Hex, Integer) :-
hex_bytes(Hex, Bytes),
foldl(plus_times256, Bytes, 0, Integer).
plus_times256(B, S0, S) :- S #= S0*256 + B.