-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathcryptonote.php
304 lines (270 loc) · 9.95 KB
/
cryptonote.php
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
<?php
/*
Copyright (c) 2018 Monero-Integrations
*/
require_once("SHA3.php");
require_once("ed25519.php");
require_once("base58.php");
class Cryptonote
{
protected $ed25519;
public function __construct()
{
$this->ed25519 = new ed25519();
$this->base58 = new base58();
}
/*
* @param string Hex encoded string of the data to hash
* @return string Hex encoded string of the hashed data
*
*/
public function keccak_256($message)
{
$keccak256 = SHA3::init (SHA3::KECCAK_256);
$keccak256->absorb (hex2bin($message));
return bin2hex ($keccak256->squeeze (32)) ;
}
/*
* @return string A hex encoded string of 32 random bytes
*
*/
public function gen_new_hex_seed()
{
$bytes = random_bytes(32);
return bin2hex($bytes);
}
public function sc_reduce($input)
{
$integer = $this->ed25519->decodeint(hex2bin($input));
$modulo = bcmod($integer , $this->ed25519->l);
$result = bin2hex($this->ed25519->encodeint($modulo));
return $result;
}
/*
* Hs in the cryptonote white paper
*
* @param string Hex encoded data to hash
*
* @return string A 32 byte encoded integer
*/
public function hash_to_scalar($data)
{
$hash = $this->keccak_256($data);
$scalar = $this->sc_reduce($hash);
return $scalar;
}
/*
* Derive a deterministic private view key from a private spend key
* @param string A private spend key represented as a 32 byte hex string
*
* @return string A deterministic private view key represented as a 32 byte hex string
*/
public function derive_viewKey($spendKey)
{
return $this->hash_to_scalar($spendkey);
}
/*
* Generate a pair of random private keys
*
* @param string A hex string to be used as a seed (this should be random)
*
* @return array An array containing a private spend key and a deterministic view key
*/
public function gen_private_keys($seed)
{
$spendKey = $this->sc_reduce($seed);
$viewKey = $this->derive_viewKey($spendKey);
$result = array("spendKey" => $spendKey,
"viewKey" => $viewKey);
return $result;
}
/*
* Get a public key from a private key on the ed25519 curve
*
* @param string a 32 byte hex encoded private key
*
* @return string a 32 byte hex encoding of a point on the curve to be used as a public key
*/
public function pk_from_sk($privKey)
{
$keyInt = $this->ed25519->decodeint(hex2bin($privKey));
$aG = $this->ed25519->scalarmult_base($keyInt);
return bin2hex($this->ed25519->encodepoint($aG));
}
/*
* Generate key derivation
*
* @param string a 32 byte hex encoding of a point on the ed25519 curve used as a public key
* @param string a 32 byte hex encoded private key
*
* @return string The hex encoded key derivation
*/
public function gen_key_derivation($public, $private)
{
$point = $this->ed25519->scalarmult($this->ed25519->decodepoint(hex2bin($public)), $this->ed25519->decodeint(hex2bin($private)));
$res = $this->ed25519->scalarmult($point, 8);
return bin2hex($this->ed25519->encodepoint($res));
}
public function encode_varint($data)
{
$orig = $data;
if ($data < 0x80)
{
return bin2hex(pack('C', $data));
}
$encodedBytes = [];
while ($data > 0)
{
$encodedBytes[] = 0x80 | ($data & 0x7f);
$data >>= 7;
}
$encodedBytes[count($encodedBytes)-1] &= 0x7f;
$bytes = call_user_func_array('pack', array_merge(array('C*'), $encodedBytes));;
return bin2hex($bytes);
}
public function derivation_to_scalar($der, $index)
{
$encoded = $this->encode_varint($index);
$data = $der . $encoded;
return $this->hash_to_scalar($data);
}
// this is a one way function used for both encrypting and decrypting 8 byte payment IDs
public function stealth_payment_id($payment_id, $tx_pub_key, $viewkey)
{
if(strlen($payment_id) != 16)
{
throw new Exception("Error: Incorrect payment ID size. Should be 8 bytes");
}
$der = $this->gen_key_derivation($tx_pub_key, $viewkey);
$data = $der . '8d';
$hash = $this->keccak_256($data);
$key = substr($hash, 0, 16);
$result = bin2hex(pack('H*',$payment_id) ^ pack('H*',$key));
return $result;
}
// takes transaction extra field as hex string and returns transaction public key 'R' as hex string
public function txpub_from_extra($extra)
{
$parsed = array_map("hexdec", str_split($extra, 2));
if($parsed[0] == 1)
{
return substr($extra, 2, 64);
}
if($parsed[0] == 2)
{
if($parsed[0] == 2 || $parsed[2] == 1)
{
$offset = (($parsed[1] + 2) *2) + 2;
return substr($extra, (($parsed[1] + 2) *2) + 2, 64);
}
}
}
public function derive_public_key($der, $index, $pub)
{
$scalar = $this->derivation_to_scalar($der, $index);
$sG = $this->ed25519->scalarmult_base($this->ed25519->decodeint(hex2bin($scalar)));
$pubPoint = $this->ed25519->decodepoint(hex2bin($pub));
$key = $this->ed25519->encodepoint($this->ed25519->edwards($pubPoint, $sG));
return bin2hex($key);
}
/*
* Perform the calculation P = P' as described in the cryptonote whitepaper
*
* @param string 32 byte transaction public key R
* @param string 32 byte receiver private view key a
* @param string 32 byte receiver public spend key B
* @param int output index
* @param string output you want to check against P
*/
public function is_output_mine($txPublic, $privViewkey, $publicSpendkey, $index, $P)
{
$derivation = $this->gen_key_derivation($txPublic, $privViewkey);
$Pprime = $this->derive_public_key($derivation, $index, $publicSpendkey);
if($P == $Pprime)
{
return true;
}
else
return false;
}
/*
* Create a valid base58 encoded Monero address from public keys
*
* @param string Public spend key
* @param string Public view key
*
* @return string Base58 encoded Monero address
*/
public function encode_address($pSpendKey, $pViewKey)
{
// mainnet network byte is 18 (0x12)
$data = "12" . $pSpendKey . $pViewKey;
$encoded = $this->base58->encode($data);
return $encoded;
}
public function verify_checksum($address)
{
$decoded = $this->base58->decode($address);
$checksum = substr($decoded, -8);
$checksum_hash = $this->keccak_256(substr($decoded, 0, 130));
$calculated = substr($checksum_hash, 0, 8);
if($checksum == $calculated){
return true;
}
else
return false;
}
/*
* Decode a base58 encoded Monero address
*
* @param string A base58 encoded Monero address
*
* @return array An array containing the Address network byte, public spend key, and public view key
*/
public function decode_address($address)
{
$decoded = $this->base58->decode($address);
if(!$this->verify_checksum($address)){
throw new Exception("Error: invalid checksum");
}
$network_byte = substr($decoded, 0, 2);
$public_spendKey = substr($decoded, 2, 64);
$public_viewKey = substr($decoded, 66, 64);
$result = array("networkByte" => $network_byte,
"spendKey" => $public_spendKey,
"viewKey" => $public_viewKey);
return $result;
}
/*
* Get an integrated address from public keys and a payment id
*
* @param string A 32 byte hex encoded public spend key
* @param string A 32 byte hex encoded public view key
* @param string An 8 byte hex string to use as a payment id
*/
public function integrated_addr_from_keys($public_spendkey, $public_viewkey, $payment_id)
{
// 0x13 is the mainnet network byte for integrated addresses
$data = "13".$public_spendkey.$public_viewkey.$payment_id;
$checksum = substr($this->keccak_256($data), 0, 8);
$result = $this->base58->encode($data.$checksum);
return $result;
}
/*
* Generate a Monero address from seed
*
* @param string Hex string to use as seed
*
* @return string A base58 encoded Monero address
*/
public function address_from_seed($hex_seed)
{
$private_keys = $this->gen_private_keys($hex_seed);
$private_viewKey = $private_keys["viewKey"];
$private_spendKey = $private_keys["spendKey"];
$public_spendKey = $this->pk_from_sk($private_spendKey);
$public_viewKey = $this->pk_from_sk($private_viewKey);
$address = $this->encode_address($public_spendKey, $public_viewKey);
return $address;
}
}