mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-12 16:45:49 +00:00
Compare commits
978 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66d21b24cd | ||
|
|
5a084ea3cc | ||
|
|
486944fa4c | ||
|
|
b63a76d298 | ||
|
|
31045039c0 | ||
|
|
6f5d1104aa | ||
|
|
3c9660203b | ||
|
|
29e1423f84 | ||
|
|
e42e67e259 | ||
|
|
ed018282c4 | ||
|
|
fbe015758c | ||
|
|
7e24a81764 | ||
|
|
9ab77e90fe | ||
|
|
ae3ed699db | ||
|
|
0c22a9753e | ||
|
|
a447f6b723 | ||
|
|
a8ef614b81 | ||
|
|
9025e8bfa8 | ||
|
|
722f4e175d | ||
|
|
1ad6e5a15f | ||
|
|
3554572db7 | ||
|
|
f1abff962f | ||
|
|
f05941fbc4 | ||
|
|
237c46d5a0 | ||
|
|
76cfb69d9f | ||
|
|
7610df0fbb | ||
|
|
8bc935aa62 | ||
|
|
24587fab9c | ||
|
|
0248475473 | ||
|
|
d2fa5c4b12 | ||
|
|
c60c0cb6e0 | ||
|
|
cdf1112666 | ||
|
|
d861bb2e34 | ||
|
|
006849a3d5 | ||
|
|
a3c1d06eba | ||
|
|
4bd1e7a2bc | ||
|
|
68643f3118 | ||
|
|
560dfc8ae6 | ||
|
|
b0459e096b | ||
|
|
2a0dfc4587 | ||
|
|
2dcd5f94fb | ||
|
|
13685d03e1 | ||
|
|
278df9025a | ||
|
|
cb608406f8 | ||
|
|
f4a55d03d3 | ||
|
|
d3b6b8127c | ||
|
|
bc1f9f8a28 | ||
|
|
9a5c9aea75 | ||
|
|
f1004c6db2 | ||
|
|
7708c64576 | ||
|
|
0527b8c981 | ||
|
|
13f89e2fcc | ||
|
|
69a0a473a6 | ||
|
|
4ab82d7e01 | ||
|
|
4be209e286 | ||
|
|
8b10325895 | ||
|
|
70bf600247 | ||
|
|
d42e06d48b | ||
|
|
9c080b6790 | ||
|
|
033257b03b | ||
|
|
39d8bcdfc2 | ||
|
|
2ddcb4e2b7 | ||
|
|
d972718a53 | ||
|
|
6abed8dd53 | ||
|
|
e74e697b45 | ||
|
|
26c59e8565 | ||
|
|
a5e83c4f23 | ||
|
|
900c4bbd2e | ||
|
|
947ec3edc2 | ||
|
|
957f10d9f1 | ||
|
|
89aa54dff8 | ||
|
|
bb76530e4b | ||
|
|
011e2cc1e3 | ||
|
|
4c594f8964 | ||
|
|
1fcfcf2392 | ||
|
|
6311abff81 | ||
|
|
ed2da57475 | ||
|
|
778ccd4805 | ||
|
|
327c35252f | ||
|
|
5e7af2fba4 | ||
|
|
dce15bc579 | ||
|
|
d5e32db954 | ||
|
|
bdfa83592b | ||
|
|
23e473b688 | ||
|
|
0dfd3a0ae0 | ||
|
|
d107092540 | ||
|
|
c2f379d3b3 | ||
|
|
57b70300f5 | ||
|
|
eeba86f9c5 | ||
|
|
e0d68e60ec | ||
|
|
254248486b | ||
|
|
1b57cc6d35 | ||
|
|
77234f256d | ||
|
|
795d31d2db | ||
|
|
f3f10fd9bd | ||
|
|
7100b4be8d | ||
|
|
b1a7200d1b | ||
|
|
5d8bb541c6 | ||
|
|
b51c59b23a | ||
|
|
2cd434e861 | ||
|
|
1599eb9629 | ||
|
|
8ef7481858 | ||
|
|
344d478b3f | ||
|
|
39b7e27aa6 | ||
|
|
b1876b4f77 | ||
|
|
db3b41d1ba | ||
|
|
02b5d14d0f | ||
|
|
0120044c96 | ||
|
|
ad6304e857 | ||
|
|
7cba84b8cf | ||
|
|
5a9a4be163 | ||
|
|
4d1a31d3c9 | ||
|
|
6e3ceec4e5 | ||
|
|
bc7d3c0af8 | ||
|
|
519ddee092 | ||
|
|
3e0fcc5b8b | ||
|
|
b1972985c4 | ||
|
|
51c42e9257 | ||
|
|
86dcbcc671 | ||
|
|
3b7cd9d84f | ||
|
|
1073ec6214 | ||
|
|
14a5e42a63 | ||
|
|
b4564a86b4 | ||
|
|
03386a61e9 | ||
|
|
8bb2623360 | ||
|
|
ab0e4188b3 | ||
|
|
42c853dbf4 | ||
|
|
ce48a1793b | ||
|
|
6177543d98 | ||
|
|
9697bfa817 | ||
|
|
70425ab5c8 | ||
|
|
7cccb451d2 | ||
|
|
a39fb9d551 | ||
|
|
8f7cdc6e4f | ||
|
|
8f7e365b03 | ||
|
|
64735e523f | ||
|
|
f126610219 | ||
|
|
2caef539ce | ||
|
|
468fb87749 | ||
|
|
4f4808ff15 | ||
|
|
e6bbca7df1 | ||
|
|
e7d1095be2 | ||
|
|
a08d5ce6e5 | ||
|
|
fec2f5578d | ||
|
|
4869e30914 | ||
|
|
e1f31765e7 | ||
|
|
a3668defa8 | ||
|
|
765ff9fa32 | ||
|
|
dd04177f83 | ||
|
|
2e2ab6bffc | ||
|
|
934cacfc1b | ||
|
|
9800fd8f11 | ||
|
|
3e84996788 | ||
|
|
5a3f55d774 | ||
|
|
dbddc314a6 | ||
|
|
c98f875811 | ||
|
|
29a1ffb3b8 | ||
|
|
17770ad4c9 | ||
|
|
cc9ed435eb | ||
|
|
27a723b453 | ||
|
|
af6c9b6bd2 | ||
|
|
2d3bbecb05 | ||
|
|
51e4cb15b4 | ||
|
|
5ce91a027c | ||
|
|
3cb337e7ec | ||
|
|
c29f92f05b | ||
|
|
01903cc6d2 | ||
|
|
fff7a6bc9e | ||
|
|
678c67622d | ||
|
|
2a6aec94fb | ||
|
|
bc52f33e9c | ||
|
|
006beeb5f9 | ||
|
|
ff85b3c4c9 | ||
|
|
6c7b2b17dc | ||
|
|
131de6661c | ||
|
|
d416f31801 | ||
|
|
8885a9e3e5 | ||
|
|
27e100f4ee | ||
|
|
40dc49bd63 | ||
|
|
989509dc07 | ||
|
|
9c3f5fbcd2 | ||
|
|
0917f66cb2 | ||
|
|
66c56df7dc | ||
|
|
b5fdfa2604 | ||
|
|
a0d4a3c84d | ||
|
|
d8374b2f49 | ||
|
|
a2a2162f48 | ||
|
|
d845d094db | ||
|
|
81e805fcb9 | ||
|
|
81283eeb84 | ||
|
|
60069d0a28 | ||
|
|
7c0561d17f | ||
|
|
45ac10b215 | ||
|
|
47f5943cf7 | ||
|
|
73d30242c9 | ||
|
|
5a4e33a02d | ||
|
|
edbbbec8f3 | ||
|
|
f8811f27a0 | ||
|
|
ad6138a14e | ||
|
|
e6fec67ce9 | ||
|
|
08a09fab9c | ||
|
|
1bf06bc656 | ||
|
|
cf46808557 | ||
|
|
df5a8656f6 | ||
|
|
b41f00515b | ||
|
|
2b22b49f83 | ||
|
|
4c0eda95c6 | ||
|
|
92e4644d60 | ||
|
|
11b67b15e4 | ||
|
|
565cd59f13 | ||
|
|
f4643c7b52 | ||
|
|
a292c2841c | ||
|
|
edd57a89c1 | ||
|
|
0d3bc96672 | ||
|
|
cc96d21da6 | ||
|
|
ed4e07907f | ||
|
|
da6e4be815 | ||
|
|
391cba18b6 | ||
|
|
49f55cea48 | ||
|
|
fc361841b0 | ||
|
|
5db493df1c | ||
|
|
ca317f01db | ||
|
|
2687830623 | ||
|
|
4d9603ae7b | ||
|
|
61087c9406 | ||
|
|
4d6251be37 | ||
|
|
18b817c284 | ||
|
|
61649b1428 | ||
|
|
c9f82be54c | ||
|
|
416dc44d05 | ||
|
|
97712bfe96 | ||
|
|
f923a62f54 | ||
|
|
a41c5ddc62 | ||
|
|
564f248fe0 | ||
|
|
e712034c0f | ||
|
|
ca88298b76 | ||
|
|
5ac21f993e | ||
|
|
5eb12c9d28 | ||
|
|
e12bea4b27 | ||
|
|
cad9521049 | ||
|
|
0847002c96 | ||
|
|
c3783533e9 | ||
|
|
85a7e935b2 | ||
|
|
43658264a8 | ||
|
|
b00f5c5a1c | ||
|
|
7af758bf88 | ||
|
|
c1a0be2402 | ||
|
|
1d4bcd4e0f | ||
|
|
4450ef822e | ||
|
|
ef74c7ca11 | ||
|
|
27eadc5587 | ||
|
|
0b03de66e7 | ||
|
|
cfcea4affb | ||
|
|
4e13170123 | ||
|
|
fe4cf94b62 | ||
|
|
f4233d7615 | ||
|
|
6e5bebfe81 | ||
|
|
21b0e09837 | ||
|
|
7b243dff03 | ||
|
|
fbe67df069 | ||
|
|
ea82c8cce3 | ||
|
|
e5322fb8e4 | ||
|
|
c5e6c5819b | ||
|
|
c3975dfc68 | ||
|
|
ddf7ca78ee | ||
|
|
71d8b6c9bf | ||
|
|
f11cd65494 | ||
|
|
59f7d49b80 | ||
|
|
447ae3f38f | ||
|
|
b2c4f935e7 | ||
|
|
5a85385db6 | ||
|
|
6dcd9e9014 | ||
|
|
eebfe02163 | ||
|
|
19294f5435 | ||
|
|
ef17cd86a8 | ||
|
|
ac62a336ea | ||
|
|
bdb7454737 | ||
|
|
4e9082f4d9 | ||
|
|
7daa4b4c3b | ||
|
|
7be13bebfc | ||
|
|
aea75f2beb | ||
|
|
cb59f86d4c | ||
|
|
8f340c1cde | ||
|
|
76659b613b | ||
|
|
79c5428da2 | ||
|
|
ca2d137d52 | ||
|
|
9789f76f64 | ||
|
|
df6eee1084 | ||
|
|
09461fb3c8 | ||
|
|
5607f2d379 | ||
|
|
3b7f556887 | ||
|
|
e84633de13 | ||
|
|
2b2267c46e | ||
|
|
37090716d3 | ||
|
|
3535ce1b04 | ||
|
|
dda865b6f2 | ||
|
|
9115a7193d | ||
|
|
6d347bcec0 | ||
|
|
b96d26acc2 | ||
|
|
5dbfe04a9a | ||
|
|
7e11b4e03d | ||
|
|
e2eaf9718c | ||
|
|
2576abe06e | ||
|
|
07a15d7b91 | ||
|
|
c5fdb3e2f6 | ||
|
|
0b068dab6a | ||
|
|
b4497bcb08 | ||
|
|
3baea7752f | ||
|
|
05cce3dbab | ||
|
|
d23dca2ef1 | ||
|
|
bcf93e230e | ||
|
|
4bd39b9bb1 | ||
|
|
8797cdb27e | ||
|
|
ab259fa519 | ||
|
|
585ca4160d | ||
|
|
5fe1ebdd45 | ||
|
|
a0ba289848 | ||
|
|
b6f0aa3914 | ||
|
|
a47eef3283 | ||
|
|
e0bcf19340 | ||
|
|
fa9305626b | ||
|
|
a573465e41 | ||
|
|
9527d6ed22 | ||
|
|
3ef60e0391 | ||
|
|
6028115e52 | ||
|
|
fd0dc3b330 | ||
|
|
3ebcadfad4 | ||
|
|
301b34a923 | ||
|
|
f963d266a2 | ||
|
|
309957a6cf | ||
|
|
47094f84d7 | ||
|
|
68c86bf672 | ||
|
|
e6782f4563 | ||
|
|
1b2b19381b | ||
|
|
d495f397b0 | ||
|
|
f1a58de348 | ||
|
|
01c6417425 | ||
|
|
6ae186951b | ||
|
|
8d77ff9af7 | ||
|
|
eeb5e22e9a | ||
|
|
fc5284cc88 | ||
|
|
6a7eb132bd | ||
|
|
f8519584d2 | ||
|
|
58b307411e | ||
|
|
c7dd3cc70e | ||
|
|
dc62dbe022 | ||
|
|
e50ddd6237 | ||
|
|
6a55dbc55e | ||
|
|
4e7cf460ec | ||
|
|
87dd8d535a | ||
|
|
ce6010ecd3 | ||
|
|
ac5f146187 | ||
|
|
14b2697a52 | ||
|
|
a4078e10e9 | ||
|
|
06796aa7c0 | ||
|
|
62d4be0185 | ||
|
|
2e0536ea2d | ||
|
|
94c7408b5b | ||
|
|
1186b4a314 | ||
|
|
1cccf01bf2 | ||
|
|
8f7cda3c2c | ||
|
|
c95ac13946 | ||
|
|
ee1ce36045 | ||
|
|
a4f22d8b42 | ||
|
|
2afce7cf54 | ||
|
|
6150721951 | ||
|
|
61b3ef0205 | ||
|
|
06108ffee3 | ||
|
|
726b309085 | ||
|
|
ff14b55ea5 | ||
|
|
bb82eb9219 | ||
|
|
abc43f80e0 | ||
|
|
f6343dc1d1 | ||
|
|
182e1863f4 | ||
|
|
0a8d4ad587 | ||
|
|
48e49ac42b | ||
|
|
1579d58edf | ||
|
|
61cb21188d | ||
|
|
49d50c02d4 | ||
|
|
b7f7e6dc60 | ||
|
|
e49bb4e527 | ||
|
|
44a9724b2a | ||
|
|
7af4a376a2 | ||
|
|
77e69efe19 | ||
|
|
da8061ed52 | ||
|
|
90d65573b5 | ||
|
|
838180a498 | ||
|
|
5ebab2fe28 | ||
|
|
0ff0004d42 | ||
|
|
b931b92773 | ||
|
|
ad08e20085 | ||
|
|
c1c18c465d | ||
|
|
a71fc07ba5 | ||
|
|
89c4839fa5 | ||
|
|
41ee43740e | ||
|
|
179c215b15 | ||
|
|
9100e8ecc0 | ||
|
|
fb213e5818 | ||
|
|
ac12e3fb5c | ||
|
|
caa78b11dd | ||
|
|
61586a4185 | ||
|
|
a7df5248c9 | ||
|
|
cf53ec9da8 | ||
|
|
46e966fb7f | ||
|
|
01e38ed4ca | ||
|
|
01459061ee | ||
|
|
3ef105e077 | ||
|
|
decebe3d2e | ||
|
|
2a832777a7 | ||
|
|
6a718c4384 | ||
|
|
a1face76b7 | ||
|
|
8104f71162 | ||
|
|
1ce2a517aa | ||
|
|
53c7836a7a | ||
|
|
160b6e8a51 | ||
|
|
b1f6284813 | ||
|
|
d104ebb6f5 | ||
|
|
017713c435 | ||
|
|
644ca2b472 | ||
|
|
453ff91065 | ||
|
|
8b79ec0e5a | ||
|
|
95b7858c8f | ||
|
|
5ef7e5462e | ||
|
|
54a2655bc2 | ||
|
|
785a066ebb | ||
|
|
fa60b182a3 | ||
|
|
dbc965de3c | ||
|
|
77814b791d | ||
|
|
ddf12d43c3 | ||
|
|
f5b59b4268 | ||
|
|
58e14f3bb3 | ||
|
|
1c02166662 | ||
|
|
0e5c29269f | ||
|
|
9a349a3d55 | ||
|
|
d5e01adbf9 | ||
|
|
61bc01ae12 | ||
|
|
c3568de8b3 | ||
|
|
eca5ac7611 | ||
|
|
a8e0cb2e0b | ||
|
|
799bb5faeb | ||
|
|
0b5c0722e0 | ||
|
|
3964e4522e | ||
|
|
bdb299e085 | ||
|
|
349ca81cc9 | ||
|
|
77a323aaae | ||
|
|
8be08b5e73 | ||
|
|
b53b05496d | ||
|
|
bd2dba7f15 | ||
|
|
81e0e2672d | ||
|
|
bd65c6e6d0 | ||
|
|
396d7b07d8 | ||
|
|
55184162d1 | ||
|
|
beffd0864a | ||
|
|
5ba7c31e4c | ||
|
|
b26129db72 | ||
|
|
1693a57845 | ||
|
|
7ea1ba168d | ||
|
|
7fe530e82c | ||
|
|
7e4ae26b8f | ||
|
|
963e1d58cb | ||
|
|
cc6f683590 | ||
|
|
da3af124ba | ||
|
|
b342cf1edf | ||
|
|
a1989b3931 | ||
|
|
80bdce970a | ||
|
|
7cf80a468d | ||
|
|
bee632e1e2 | ||
|
|
7901e12b0a | ||
|
|
8979a3cf02 | ||
|
|
9025119f8d | ||
|
|
fdaa63c132 | ||
|
|
9e825e927c | ||
|
|
4929d63073 | ||
|
|
5b0dd33fa8 | ||
|
|
5203a1e868 | ||
|
|
27645c234a | ||
|
|
5280d994a2 | ||
|
|
12f43a5334 | ||
|
|
4f0399180c | ||
|
|
ea11d34254 | ||
|
|
a222f2be98 | ||
|
|
a48a25e236 | ||
|
|
e4f9be5af8 | ||
|
|
473d8a8d8c | ||
|
|
41ea820ae0 | ||
|
|
0558ad689a | ||
|
|
3199aa438a | ||
|
|
c3f630c27f | ||
|
|
cf3a21a712 | ||
|
|
d8504a3001 | ||
|
|
a2b07d5edd | ||
|
|
13a6a2c335 | ||
|
|
e19be192bd | ||
|
|
c32216c9e5 | ||
|
|
904082a86c | ||
|
|
f56a20d697 | ||
|
|
8275e036c9 | ||
|
|
903e480130 | ||
|
|
30fd0e7bff | ||
|
|
fbdef6eea0 | ||
|
|
5a04ce9629 | ||
|
|
693e2aaae7 | ||
|
|
43deeaf5fb | ||
|
|
cbba7727f2 | ||
|
|
52e1665e72 | ||
|
|
66ea770287 | ||
|
|
18efa5d742 | ||
|
|
802212bbdc | ||
|
|
7f59fb917c | ||
|
|
6ebaec31a5 | ||
|
|
14f409ff56 | ||
|
|
8ffd0b13a3 | ||
|
|
969873441e | ||
|
|
282ac6d8ab | ||
|
|
1e3c96b14f | ||
|
|
b14fab8aa7 | ||
|
|
be33b1be60 | ||
|
|
06288e798e | ||
|
|
0de7d84862 | ||
|
|
58afce517a | ||
|
|
250e987fd9 | ||
|
|
87ba2abc9a | ||
|
|
716fd0b938 | ||
|
|
893fc4c168 | ||
|
|
6f5cf8506f | ||
|
|
c808cb0a1c | ||
|
|
5f677a86a7 | ||
|
|
11540f8cd9 | ||
|
|
9d6ccdcab1 | ||
|
|
2267e599c0 | ||
|
|
687bcc8539 | ||
|
|
e64dba839c | ||
|
|
a651b6154d | ||
|
|
4b5e75a8ad | ||
|
|
bdd24bada9 | ||
|
|
6e4a9179c1 | ||
|
|
b0c38ff566 | ||
|
|
e97a3966c1 | ||
|
|
d0aa86bf97 | ||
|
|
b17eb8ed0d | ||
|
|
7dcfa0a232 | ||
|
|
05c466bebe | ||
|
|
0f7c80d76a | ||
|
|
99599ba673 | ||
|
|
85f75b4543 | ||
|
|
37a22186ca | ||
|
|
007c2e7e5c | ||
|
|
a7581b1ec8 | ||
|
|
06c662cc14 | ||
|
|
7feafaa320 | ||
|
|
1c06baf5db | ||
|
|
a188ae6ace | ||
|
|
c0db5e251a | ||
|
|
bc427204c6 | ||
|
|
976de00242 | ||
|
|
a098b0cf02 | ||
|
|
70d28fc15e | ||
|
|
815eed427f | ||
|
|
55e74671a7 | ||
|
|
e595291c95 | ||
|
|
47db68ff45 | ||
|
|
e227a541e7 | ||
|
|
7a88165e16 | ||
|
|
5ed148af75 | ||
|
|
1329de5344 | ||
|
|
a003d261ea | ||
|
|
37065123e5 | ||
|
|
44dd0ca4a3 | ||
|
|
15304237d1 | ||
|
|
acfc1bbb8c | ||
|
|
75a732e0ad | ||
|
|
a46bff3c5e | ||
|
|
8fcb694f1d | ||
|
|
ef758b17b1 | ||
|
|
b9646c51b8 | ||
|
|
e2f59ce975 | ||
|
|
c3ceba1f05 | ||
|
|
573b38cc19 | ||
|
|
5ca5c73c34 | ||
|
|
5a15c5b1d5 | ||
|
|
c60097f8c5 | ||
|
|
7f13a71252 | ||
|
|
5c8550b364 | ||
|
|
f27be38ca9 | ||
|
|
aed6b7754e | ||
|
|
0772ffb6ed | ||
|
|
37e6d95cbc | ||
|
|
644b611b9b | ||
|
|
b21be326fe | ||
|
|
3d9cea62ff | ||
|
|
a15da9ade1 | ||
|
|
a07b76a28c | ||
|
|
372c508911 | ||
|
|
257e9da563 | ||
|
|
cdfb7989b0 | ||
|
|
b01c15847c | ||
|
|
23528293e0 | ||
|
|
430dfef23d | ||
|
|
215a3f1669 | ||
|
|
f678f47155 | ||
|
|
fa07601a2a | ||
|
|
0d4357232c | ||
|
|
014514a4da | ||
|
|
86c35d7d9c | ||
|
|
9f76907f51 | ||
|
|
4e67167394 | ||
|
|
292b6825fc | ||
|
|
aed87f7437 | ||
|
|
c95d976e84 | ||
|
|
7bbab0759a | ||
|
|
2396a53b03 | ||
|
|
43ee82af57 | ||
|
|
3308e0ea92 | ||
|
|
61a6db1641 | ||
|
|
43eb4c3bfe | ||
|
|
8538b3ccf2 | ||
|
|
f9bb7094e9 | ||
|
|
54b7099d01 | ||
|
|
4ce04a5c90 | ||
|
|
19e29722dc | ||
|
|
99351aa2dc | ||
|
|
ef6aca8d12 | ||
|
|
b44f22c87f | ||
|
|
ca653c42f1 | ||
|
|
5544752dcd | ||
|
|
4082bd2a0a | ||
|
|
626164a8ab | ||
|
|
848c203b81 | ||
|
|
9f92a8dcce | ||
|
|
c8bb6e2a6a | ||
|
|
f135021c52 | ||
|
|
f7dc3b9fa1 | ||
|
|
967317f37e | ||
|
|
b5d29533ee | ||
|
|
a5c1dd1089 | ||
|
|
fd614ce0df | ||
|
|
eaf01312e4 | ||
|
|
5c3f3ff0f1 | ||
|
|
4a2515a6b2 | ||
|
|
c3a8d37e1c | ||
|
|
66af0a0b8e | ||
|
|
1a093c165e | ||
|
|
7182c85f11 | ||
|
|
eed4a932bd | ||
|
|
b5f4fdf918 | ||
|
|
ce9e2c9539 | ||
|
|
1cf0fbbde4 | ||
|
|
10d5725919 | ||
|
|
e9ec3ec3ef | ||
|
|
95c7acb210 | ||
|
|
65b035267c | ||
|
|
f10bc6718c | ||
|
|
5229a3d507 | ||
|
|
e7b6050b82 | ||
|
|
38a6e8b009 | ||
|
|
96b46d2394 | ||
|
|
d401a70a2a | ||
|
|
9ad5337e7b | ||
|
|
03c7b6b2aa | ||
|
|
94e5c3ddf1 | ||
|
|
caac293be5 | ||
|
|
faae634853 | ||
|
|
627acdbfde | ||
|
|
a7532b5f55 | ||
|
|
9596787cad | ||
|
|
6cdeacdb90 | ||
|
|
1b6945f74d | ||
|
|
675a599876 | ||
|
|
94c57bc0d8 | ||
|
|
667919cea1 | ||
|
|
9870761e32 | ||
|
|
8ef6fdea14 | ||
|
|
5c911367b7 | ||
|
|
e34cf6ebcf | ||
|
|
620a33b7bd | ||
|
|
e65bec78a1 | ||
|
|
d33c8e46af | ||
|
|
472a5091ae | ||
|
|
ff5e9fcaa9 | ||
|
|
4a027bdcdf | ||
|
|
dbdfc262b6 | ||
|
|
dc2b69d2c2 | ||
|
|
9a78d36b72 | ||
|
|
d14346ba15 | ||
|
|
992b4c53b8 | ||
|
|
385113c2f5 | ||
|
|
dc04bbe737 | ||
|
|
b6a66e1820 | ||
|
|
bcceceb2e0 | ||
|
|
19c0585ff0 | ||
|
|
97df7472f5 | ||
|
|
f05577d7df | ||
|
|
4d94aef80c | ||
|
|
0902640fc0 | ||
|
|
bcd5e38976 | ||
|
|
dfedc86fb7 | ||
|
|
78d110c8d1 | ||
|
|
4e6ae229c0 | ||
|
|
49cd062cb7 | ||
|
|
1fa196037e | ||
|
|
674ce90eaf | ||
|
|
a306407588 | ||
|
|
5c81441634 | ||
|
|
cff9902802 | ||
|
|
08911ec2ee | ||
|
|
79b698f232 | ||
|
|
5253278c29 | ||
|
|
48aa5b6c01 | ||
|
|
7e5c4af53d | ||
|
|
9b3bb77d0e | ||
|
|
a72995642c | ||
|
|
12562d68f8 | ||
|
|
8805610413 | ||
|
|
b9082a425f | ||
|
|
c1f5705ef8 | ||
|
|
69758cd631 | ||
|
|
05f3a97042 | ||
|
|
1db2187e79 | ||
|
|
ae68e3a1a6 | ||
|
|
96ed994932 | ||
|
|
b2580ec178 | ||
|
|
e065238758 | ||
|
|
34ea528ade | ||
|
|
1355dc135f | ||
|
|
9652a99589 | ||
|
|
8dfdced942 | ||
|
|
e779f98fb8 | ||
|
|
f633beafbf | ||
|
|
81d380fb90 | ||
|
|
4be5468485 | ||
|
|
a8759f1390 | ||
|
|
5b1b41ae80 | ||
|
|
75ed909aeb | ||
|
|
069852a501 | ||
|
|
5def7ba917 | ||
|
|
74ef8f8400 | ||
|
|
0e5431468c | ||
|
|
e65f975e29 | ||
|
|
0f80f6d4ae | ||
|
|
3491fec469 | ||
|
|
4df88b6df5 | ||
|
|
2a763f8f03 | ||
|
|
a306d0d0e1 | ||
|
|
2e39f6d4a9 | ||
|
|
03930ecc9b | ||
|
|
c3215e6387 | ||
|
|
31340c8bc1 | ||
|
|
fd07103e9a | ||
|
|
43319267ad | ||
|
|
37da90e81d | ||
|
|
58bc1b1682 | ||
|
|
60e2aa613a | ||
|
|
3f3b8dbfb9 | ||
|
|
c5390100f5 | ||
|
|
6f8d33005a | ||
|
|
1c5b6b365a | ||
|
|
8447d2c22d | ||
|
|
04b8bf277c | ||
|
|
fb3e15a079 | ||
|
|
8959d9167f | ||
|
|
32d404bd44 | ||
|
|
2415c668e4 | ||
|
|
5c19ad2885 | ||
|
|
73d41872cb | ||
|
|
1eb2652edf | ||
|
|
997e561389 | ||
|
|
45fff5642d | ||
|
|
179dba81cc | ||
|
|
25f9d27206 | ||
|
|
fee2d3ad0e | ||
|
|
6e54f34c4b | ||
|
|
7d631e5864 | ||
|
|
7ad0b0b093 | ||
|
|
9571e7c130 | ||
|
|
b21d7aef0d | ||
|
|
137a6a757e | ||
|
|
12a5c3c701 | ||
|
|
37b653eae6 | ||
|
|
f24be618d8 | ||
|
|
f077624212 | ||
|
|
b4c120cea4 | ||
|
|
5f69cad0cc | ||
|
|
76b5ca9a12 | ||
|
|
37234d2751 | ||
|
|
abfab78f96 | ||
|
|
53d704310a | ||
|
|
ab1c409022 | ||
|
|
bf940fdec5 | ||
|
|
6015ba7d25 | ||
|
|
72eb558f6a | ||
|
|
d74a7e9ea1 | ||
|
|
f259c7ebd7 | ||
|
|
8d13065cf4 | ||
|
|
5c36be59e5 | ||
|
|
d2559dcdb8 | ||
|
|
6194d9bcd9 | ||
|
|
64df821b38 | ||
|
|
0986b37267 | ||
|
|
26220239fa | ||
|
|
73fae67022 | ||
|
|
6dc15c6951 | ||
|
|
bc600a27b3 | ||
|
|
c8e0ef01ac | ||
|
|
db28f9ae46 | ||
|
|
7b4182b322 | ||
|
|
0753d9cf4a | ||
|
|
f337aa6a16 | ||
|
|
7080611247 | ||
|
|
bab9fec836 | ||
|
|
8dd1b62d78 | ||
|
|
9eeb3faba3 | ||
|
|
bba2e10e94 | ||
|
|
943251b387 | ||
|
|
8b76db3942 | ||
|
|
2463fd9588 | ||
|
|
56a8ad3aa5 | ||
|
|
57aa1773cb | ||
|
|
1f22456aed | ||
|
|
e784ad6d27 | ||
|
|
068aea3ac4 | ||
|
|
40eccdb2fd | ||
|
|
3f2afdc39d | ||
|
|
bcaabd36be | ||
|
|
21aed214a7 | ||
|
|
d761208878 | ||
|
|
2f7ce2841c | ||
|
|
3fa3f6fb94 | ||
|
|
6f9c103d78 | ||
|
|
b131bd413c | ||
|
|
8e7d5d37b9 | ||
|
|
5f7a4fe042 | ||
|
|
24ef629da3 | ||
|
|
7e1e8a9503 | ||
|
|
3807c1ba6a | ||
|
|
c3753ffc6a | ||
|
|
41b66fc585 | ||
|
|
95e547aff9 | ||
|
|
b6580dfe40 | ||
|
|
6330777076 | ||
|
|
2858579820 | ||
|
|
2e7fcb0e76 | ||
|
|
8cab1ae200 | ||
|
|
dad760f9ba | ||
|
|
e944f0f5c4 | ||
|
|
379638da43 | ||
|
|
121bca2f1d | ||
|
|
7bf83e5c48 | ||
|
|
33c12ec93f | ||
|
|
7a27bbb78c | ||
|
|
6ce83107c8 | ||
|
|
41fb5e5b22 | ||
|
|
8588e0c78f | ||
|
|
2091d3ed29 | ||
|
|
529aac8c37 | ||
|
|
428d31c4db | ||
|
|
aeba9ba05e | ||
|
|
bf95285d90 | ||
|
|
45292847ee | ||
|
|
aec3df1bf9 | ||
|
|
8b1407613a | ||
|
|
2d68ce5f1b | ||
|
|
0a1d1f2ed4 | ||
|
|
25401bd08d | ||
|
|
0ce98b126f | ||
|
|
db442f613e | ||
|
|
b31d969d63 | ||
|
|
8a8159ee0b | ||
|
|
74c6852743 | ||
|
|
b9e29e8b6c | ||
|
|
9246449590 | ||
|
|
e3a6913cb0 | ||
|
|
6231d0102b | ||
|
|
9630d92e0a | ||
|
|
0b7ff197c8 | ||
|
|
b55c547bb9 | ||
|
|
9b1c47278c | ||
|
|
aa6a9e9559 | ||
|
|
bddf4a8673 | ||
|
|
0aee56f4f1 | ||
|
|
fb94ef3cf3 | ||
|
|
7ff1e89824 | ||
|
|
b5fcab50d1 | ||
|
|
69afa608bf | ||
|
|
9677b09ecc | ||
|
|
1f3eb57fa4 | ||
|
|
a501400caa | ||
|
|
69e44a66bb | ||
|
|
abec5386a6 | ||
|
|
2e15bce98b | ||
|
|
10ac0a5e42 | ||
|
|
b723e031d1 | ||
|
|
18c02cbb42 | ||
|
|
d89f04a622 | ||
|
|
ceea368a5c | ||
|
|
ef83019a4b | ||
|
|
28c90f1923 | ||
|
|
8bc6a3cbcf | ||
|
|
a46d08c6f4 | ||
|
|
7d63cb52cb | ||
|
|
81ff4eec2d | ||
|
|
7a6d46acd3 | ||
|
|
700c6f0430 | ||
|
|
6688ad5d8d | ||
|
|
f3e1b6b39f | ||
|
|
d43bc13f48 | ||
|
|
a4ff3cbaac | ||
|
|
370fab5858 | ||
|
|
75d00f1778 | ||
|
|
d7ecbbf648 | ||
|
|
bff8415ea5 | ||
|
|
0794f7b629 | ||
|
|
b5a50aeba9 | ||
|
|
f6cfcb6217 | ||
|
|
fd3a583abf | ||
|
|
b551753fe4 | ||
|
|
b6283a554d | ||
|
|
a65ba8f87d | ||
|
|
ffd23d34ab | ||
|
|
75859a2c4d | ||
|
|
166e35bf0e | ||
|
|
74da0c8a52 | ||
|
|
d5fb086b51 | ||
|
|
6fa2a220e7 | ||
|
|
27fed3e737 | ||
|
|
13b62d72c6 | ||
|
|
4c6c967576 | ||
|
|
009143f724 | ||
|
|
c545e19e40 | ||
|
|
87790b330e | ||
|
|
c9d081f2c6 | ||
|
|
222d49c4d8 | ||
|
|
ce8fd9ddbe | ||
|
|
ccd1758284 | ||
|
|
ba48078f9e | ||
|
|
f35df54eab | ||
|
|
f1334c6b92 | ||
|
|
7a5631a2ce | ||
|
|
9c9f8fae14 | ||
|
|
dc51b8ba48 | ||
|
|
e3edd5267d | ||
|
|
8b69dfd154 | ||
|
|
8333c3fbfc | ||
|
|
083377651f | ||
|
|
0c18f6157d | ||
|
|
66d71efa78 | ||
|
|
0811dec65c | ||
|
|
b3822cc4c5 | ||
|
|
942bfe88c1 | ||
|
|
63038d3603 | ||
|
|
0f5fdbc772 | ||
|
|
36f8ca1073 | ||
|
|
1e288c31af | ||
|
|
e8144128d0 | ||
|
|
e5421effd5 | ||
|
|
48a2bb9fe4 | ||
|
|
67d100796d | ||
|
|
0f91bf0e98 | ||
|
|
1aaee7526b | ||
|
|
fd0ce52cf6 | ||
|
|
0fd973f58b | ||
|
|
a99856bec0 | ||
|
|
eac195ec18 | ||
|
|
ab5d64ff12 | ||
|
|
5f017b2b85 | ||
|
|
bb6ec3b5c9 | ||
|
|
d870264230 | ||
|
|
aad97c936d | ||
|
|
b78835f195 | ||
|
|
a480e76353 | ||
|
|
8294c78db4 | ||
|
|
3ea426d45f | ||
|
|
bc74543ca2 | ||
|
|
c69966035b | ||
|
|
a1bef6248a | ||
|
|
2f4a9c2c26 | ||
|
|
342d5a1aa1 | ||
|
|
a5206d2959 | ||
|
|
4e876511a3 |
17
.gitignore
vendored
17
.gitignore
vendored
@@ -17,7 +17,7 @@
|
||||
|
||||
# Ignore object files.
|
||||
*.o
|
||||
build
|
||||
build/*.js
|
||||
tags
|
||||
bin/rippled
|
||||
Debug/*.*
|
||||
@@ -37,3 +37,18 @@ db/*.db-*
|
||||
rippled.cfg
|
||||
validators.txt
|
||||
test/config.js
|
||||
|
||||
# Ignore coverage files
|
||||
/lib-cov
|
||||
/src-cov
|
||||
/coverage.html
|
||||
/coverage
|
||||
|
||||
# Ignore IntelliJ files
|
||||
.idea
|
||||
|
||||
# Ignore npm-debug
|
||||
npm-debug.log
|
||||
|
||||
# Ignore dist folder, build for bower
|
||||
dist/
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
build
|
||||
deploy
|
||||
lib-cov
|
||||
coverage.html
|
||||
|
||||
13
.travis.yml
Normal file
13
.travis.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
script: npm test --coverage
|
||||
after_success:
|
||||
- npm run coveralls
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/d1ec4245f90231619d30
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: false # default: false
|
||||
100
Gruntfile.js
100
Gruntfile.js
@@ -1,100 +0,0 @@
|
||||
module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks('grunt-webpack');
|
||||
grunt.loadNpmTasks('grunt-dox');
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
meta: {
|
||||
banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
|
||||
'<%= grunt.template.today("yyyy-mm-dd") %>\n' +
|
||||
'<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' +
|
||||
'* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
|
||||
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
|
||||
},
|
||||
concat: {
|
||||
sjcl: {
|
||||
src: [
|
||||
"src/js/sjcl/core/sjcl.js",
|
||||
"src/js/sjcl/core/aes.js",
|
||||
"src/js/sjcl/core/bitArray.js",
|
||||
"src/js/sjcl/core/codecString.js",
|
||||
"src/js/sjcl/core/codecHex.js",
|
||||
"src/js/sjcl/core/codecBase64.js",
|
||||
"src/js/sjcl/core/codecBytes.js",
|
||||
"src/js/sjcl/core/sha256.js",
|
||||
"src/js/sjcl/core/sha512.js",
|
||||
"src/js/sjcl/core/sha1.js",
|
||||
"src/js/sjcl/core/ccm.js",
|
||||
// "src/js/sjcl/core/cbc.js",
|
||||
// "src/js/sjcl/core/ocb2.js",
|
||||
"src/js/sjcl/core/hmac.js",
|
||||
"src/js/sjcl/core/pbkdf2.js",
|
||||
"src/js/sjcl/core/random.js",
|
||||
"src/js/sjcl/core/convenience.js",
|
||||
"src/js/sjcl/core/bn.js",
|
||||
"src/js/sjcl/core/ecc.js",
|
||||
"src/js/sjcl/core/srp.js",
|
||||
"src/js/sjcl-custom/sjcl-secp256k1.js",
|
||||
"src/js/sjcl-custom/sjcl-ripemd160.js",
|
||||
"src/js/sjcl-custom/sjcl-extramath.js",
|
||||
"src/js/sjcl-custom/sjcl-validecc.js",
|
||||
"src/js/sjcl-custom/sjcl-ecdsa-der.js"
|
||||
],
|
||||
dest: 'build/sjcl.js'
|
||||
}
|
||||
},
|
||||
webpack: {
|
||||
options: {
|
||||
entry: "./src/js/ripple/index.js",
|
||||
output: {
|
||||
library: "ripple"
|
||||
}
|
||||
},
|
||||
lib: {
|
||||
output: {
|
||||
filename: "build/ripple-<%= pkg.version %>.js"
|
||||
}
|
||||
},
|
||||
lib_debug: {
|
||||
output: {
|
||||
filename: "build/ripple-<%= pkg.version %>-debug.js"
|
||||
},
|
||||
debug: true,
|
||||
devtool: 'eval'
|
||||
},
|
||||
lib_min: {
|
||||
output: {
|
||||
filename: "build/ripple-<%= pkg.version %>-min.js"
|
||||
},
|
||||
optimize: {
|
||||
minimize: true
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
sjcl: {
|
||||
files: ['<%= concat.sjcl.src %>'],
|
||||
tasks: 'concat:sjcl'
|
||||
},
|
||||
lib: {
|
||||
files: 'src/js/*.js',
|
||||
tasks: 'webpack'
|
||||
}
|
||||
},
|
||||
dox: {
|
||||
libdocs: {
|
||||
options: {
|
||||
title: "Test"
|
||||
},
|
||||
src: ['src/js/ripple/'],
|
||||
dest: 'build/docs'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Tasks
|
||||
grunt.registerTask('default', ['concat:sjcl', 'webpack']);
|
||||
|
||||
};
|
||||
163
Gulpfile.js
Normal file
163
Gulpfile.js
Normal file
@@ -0,0 +1,163 @@
|
||||
var gulp = require('gulp');
|
||||
var concat = require('gulp-concat');
|
||||
var uglify = require('gulp-uglify');
|
||||
var rename = require('gulp-rename');
|
||||
var webpack = require('webpack');
|
||||
var jshint = require('gulp-jshint');
|
||||
var map = require('map-stream');
|
||||
var bump = require('gulp-bump');
|
||||
var argv = require('yargs').argv;
|
||||
//var header = require('gulp-header');
|
||||
|
||||
var pkg = require('./package.json');
|
||||
|
||||
var banner = '/*! <%= pkg.name %> - v<%= pkg.version %> - '
|
||||
+ '<%= new Date().toISOString() %>\n'
|
||||
+ '<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>'
|
||||
+ '* Copyright (c) <%= new Date().getFullYear() %> <%= pkg.author.name %>;'
|
||||
+ ' Licensed <%= pkg.license %> */'
|
||||
|
||||
var sjclSrc = [
|
||||
'src/js/sjcl/core/sjcl.js',
|
||||
'src/js/sjcl/core/aes.js',
|
||||
'src/js/sjcl/core/bitArray.js',
|
||||
'src/js/sjcl/core/codecString.js',
|
||||
'src/js/sjcl/core/codecHex.js',
|
||||
'src/js/sjcl/core/codecBase64.js',
|
||||
'src/js/sjcl/core/codecBytes.js',
|
||||
'src/js/sjcl/core/sha256.js',
|
||||
'src/js/sjcl/core/sha512.js',
|
||||
'src/js/sjcl/core/sha1.js',
|
||||
'src/js/sjcl/core/ccm.js',
|
||||
// 'src/js/sjcl/core/cbc.js',
|
||||
// 'src/js/sjcl/core/ocb2.js',
|
||||
'src/js/sjcl/core/hmac.js',
|
||||
'src/js/sjcl/core/pbkdf2.js',
|
||||
'src/js/sjcl/core/random.js',
|
||||
'src/js/sjcl/core/convenience.js',
|
||||
'src/js/sjcl/core/bn.js',
|
||||
'src/js/sjcl/core/ecc.js',
|
||||
'src/js/sjcl/core/srp.js',
|
||||
'src/js/sjcl-custom/sjcl-ecc-pointextras.js',
|
||||
'src/js/sjcl-custom/sjcl-secp256k1.js',
|
||||
'src/js/sjcl-custom/sjcl-ripemd160.js',
|
||||
'src/js/sjcl-custom/sjcl-extramath.js',
|
||||
'src/js/sjcl-custom/sjcl-montgomery.js',
|
||||
'src/js/sjcl-custom/sjcl-validecc.js',
|
||||
'src/js/sjcl-custom/sjcl-ecdsa-canonical.js',
|
||||
'src/js/sjcl-custom/sjcl-ecdsa-der.js',
|
||||
'src/js/sjcl-custom/sjcl-ecdsa-recoverablepublickey.js',
|
||||
'src/js/sjcl-custom/sjcl-jacobi.js'
|
||||
];
|
||||
|
||||
gulp.task('concat-sjcl', function() {
|
||||
return gulp.src(sjclSrc)
|
||||
.pipe(concat('sjcl.js'))
|
||||
.pipe(gulp.dest('./build/'));
|
||||
});
|
||||
|
||||
gulp.task('build', [ 'concat-sjcl' ], function(callback) {
|
||||
webpack({
|
||||
cache: true,
|
||||
entry: './src/js/ripple/index.js',
|
||||
output: {
|
||||
library: 'ripple',
|
||||
path: './build/',
|
||||
filename: [ 'ripple-', '.js' ].join(pkg.version)
|
||||
},
|
||||
}, callback);
|
||||
});
|
||||
|
||||
gulp.task('bower-build', [ 'build' ], function(callback) {
|
||||
return gulp.src([ './build/ripple-', '.js' ].join(pkg.version))
|
||||
.pipe(rename('ripple.js'))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
});
|
||||
|
||||
gulp.task('bower-build-min', [ 'build-min' ], function(callback) {
|
||||
return gulp.src([ './build/ripple-', '-min.js' ].join(pkg.version))
|
||||
.pipe(rename('ripple-min.js'))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
});
|
||||
|
||||
gulp.task('bower-build-debug', [ 'build-debug' ], function(callback) {
|
||||
return gulp.src([ './build/ripple-', '-debug.js' ].join(pkg.version))
|
||||
.pipe(rename('ripple-debug.js'))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
});
|
||||
|
||||
gulp.task('bower-version', function() {
|
||||
gulp.src('./dist/bower.json')
|
||||
.pipe(bump({version: pkg.version}))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
});
|
||||
|
||||
gulp.task('version-bump', function() {
|
||||
if (!argv.type) {
|
||||
throw new Error("No type found, pass it in using the --type argument");
|
||||
}
|
||||
gulp.src('./package.json')
|
||||
.pipe(bump({type:argv.type}))
|
||||
.pipe(gulp.dest('./'));
|
||||
});
|
||||
|
||||
gulp.task('version-beta', function() {
|
||||
gulp.src('./package.json')
|
||||
.pipe(bump({version: pkg.version+'-beta'}))
|
||||
.pipe(gulp.dest('./'));
|
||||
});
|
||||
|
||||
gulp.task('build-min', [ 'build' ], function(callback) {
|
||||
return gulp.src([ './build/ripple-', '.js' ].join(pkg.version))
|
||||
.pipe(uglify())
|
||||
.pipe(rename([ 'ripple-', '-min.js' ].join(pkg.version)))
|
||||
.pipe(gulp.dest('./build/'));
|
||||
});
|
||||
|
||||
gulp.task('build-debug', [ 'concat-sjcl' ], function(callback) {
|
||||
webpack({
|
||||
cache: true,
|
||||
entry: './src/js/ripple/index.js',
|
||||
output: {
|
||||
library: 'ripple',
|
||||
path: './build/',
|
||||
filename: [ 'ripple-', '-debug.js' ].join(pkg.version)
|
||||
},
|
||||
debug: true,
|
||||
devtool: 'eval'
|
||||
}, callback);
|
||||
});
|
||||
|
||||
gulp.task('lint', function() {
|
||||
gulp.src('src/js/ripple/*.js')
|
||||
.pipe(jshint())
|
||||
.pipe(map(function(file, callback) {
|
||||
if (!file.jshint.success) {
|
||||
console.log('\nIn', file.path);
|
||||
|
||||
file.jshint.results.forEach(function(err) {
|
||||
if (err && err.error) {
|
||||
var col1 = err.error.line + ':' + err.error.character;
|
||||
var col2 = '[' + err.error.reason + ']';
|
||||
var col3 = '(' + err.error.code + ')';
|
||||
|
||||
while (col1.length < 8) {
|
||||
col1 += ' ';
|
||||
}
|
||||
|
||||
console.log(' ' + [ col1, col2, col3 ].join(' '));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
callback(null, file);
|
||||
}));
|
||||
});
|
||||
|
||||
gulp.task('watch', function() {
|
||||
gulp.watch('src/js/ripple/*', [ 'build-debug' ]);
|
||||
});
|
||||
|
||||
gulp.task('default', [ 'concat-sjcl', 'build', 'build-debug', 'build-min' ]);
|
||||
|
||||
gulp.task('bower', ['bower-build', 'bower-build-min', 'bower-build-debug', 'bower-version']);
|
||||
210
HISTORY.md
Normal file
210
HISTORY.md
Normal file
@@ -0,0 +1,210 @@
|
||||
##0.9.2
|
||||
|
||||
+ [**Breaking change**: Change accountRequest method signature](https://github.com/ripple/ripple-lib/commit/6f5d1104aa3eb440c518ec4f39e264fdce15fa15)
|
||||
|
||||
+ [Add paging behavior for account requests, `account_lines` and `account_offers`](https://github.com/ripple/ripple-lib/commit/722f4e175dbbf378e51b49142d0285f87acb22d7)
|
||||
|
||||
+ [Add max_fee setter to transactions to set max fee the submitter is willing to pay] (https://github.com/ripple/ripple-lib/commit/24587fab9c8ad3840d7aa345a7037b48839e09d7)
|
||||
|
||||
+ [Fix: cap IOU Amounts to their max and min value] (https://github.com/ripple/ripple-lib/commit/f05941fbc46fdb7c6fe7ad72927af02d527ffeed)
|
||||
|
||||
Example on how to use paging with `account_offers`:
|
||||
```
|
||||
// A valid `ledger_index` or `ledger_hash` is required to provide a reliable result.
|
||||
// Results can change between ledger closes, so the provided ledger will be used as base.
|
||||
var options = {
|
||||
account: < rippleAccount >,
|
||||
limit: < Number between 10 and 400 >,
|
||||
ledger: < valid ledger_index or ledger_hash >
|
||||
}
|
||||
|
||||
// The `marker` comes back in an account request if there are more results than are returned
|
||||
// in the current response. The amount of results per response are determined by the `limit`.
|
||||
if (marker) {
|
||||
options.marker = < marker >;
|
||||
}
|
||||
|
||||
var request = remote.requestAccountOffers(options);
|
||||
```
|
||||
|
||||
[Full working example](https://github.com/geertweening/ripple-lib-scripts/blob/master/account_offers_paging.js)
|
||||
|
||||
|
||||
##0.9.1
|
||||
|
||||
+ Switch account requests to use ledgerSelect rather than ledgerChoose ([278df90](https://github.com/ripple/ripple-lib/commit/278df9025a20228de22379a53c76ca12d40fa591))
|
||||
|
||||
+ **Deprecated** setting `ident` and `account_index` on account requests ([278df90](https://github.com/ripple/ripple-lib/commit/278df9025a20228de22379a53c76ca12d40fa591))
|
||||
|
||||
+ Change initial account transaction sequence to 1 ([a3c1d06](https://github.com/ripple/ripple-lib/commit/a3c1d06eba883dc84fe2bfe700e4309795c84cac))
|
||||
|
||||
+ Fix: instance transaction withoute remote ([d3b6b81](https://github.com/ripple/ripple-lib/commit/d3b6b8127c7b01e416b400c25abf1719bdd008ca))
|
||||
|
||||
+ Fix: account root request ledger argument ([bc1f9f8](https://github.com/ripple/ripple-lib/commit/bc1f9f8a286b187d36ebaf552694e31e73742293))
|
||||
|
||||
+ Fix: rsign.js local signing and example ([d3b6b81](https://github.com/ripple/ripple-lib/commit/d3b6b8127c7b01e416b400c25abf1719bdd008ca) and [f1004c6](https://github.com/ripple/ripple-lib/commit/f1004c6db2a0ce59bbabbb8f2b355a9fd9995fd8))
|
||||
|
||||
|
||||
##0.9.0
|
||||
|
||||
+ Add routes to the vault client for KYC attestations ([ed2da574](https://github.com/ripple/ripple-lib/commit/ed2da57475acf5e9d2cf3373858f4274832bd83f))
|
||||
|
||||
+ Currency: add `show_interest` flag to show or hide interest in `Currency.to_human()` and `Currency.to_json()` [Example use in tests](https://github.com/ripple/ripple-lib/blob/947ec3edc2e7c8f1ef097e496bf552c74366e749/test/currency-test.js#L123)
|
||||
|
||||
+ Configurable maxAttempts for transaction submission ([d107092](https://github.com/ripple/ripple-lib/commit/d10709254061e9e4416d2cb78b5cac1ec0d7ffa5))
|
||||
|
||||
+ Binformat: added missing TransactionResult options ([6abed8d](https://github.com/ripple/ripple-lib/commit/6abed8dd5311765b2eb70505dadbdf5121439ca8))
|
||||
|
||||
+ **Breaking change:** make maxLoops in seed.get_key optional. [Example use in tests](https://github.com/ripple/ripple-lib/blob/23e473b6886c457781949c825b3ff48b3984e51f/test/seed-test.js) ([23e473b](https://github.com/ripple/ripple-lib/commit/23e473b6886c457781949c825b3ff48b3984e51f))
|
||||
|
||||
+ Shrinkwrap packages for dependency locking ([2dcd5f9](2dcd5f94fbc71200eb08a5044c76ef94f7971913))
|
||||
|
||||
+ Fix: Amount.to_human() precision bugs ([4be209e](https://github.com/ripple/ripple-lib/commit/4be209e286b5b209bec7bcd1212098985e15ff2f) and [7708c64](https://github.com/ripple/ripple-lib/commit/7708c64576e70ce3ac190442daceb30e4446aab7))
|
||||
|
||||
+ Fix: change handling of requestLedger options ([57b7030](https://github.com/ripple/ripple-lib/commit/57b70300f5f0c7534ede118ddbb5d8762668a4f8))
|
||||
|
||||
|
||||
##0.8.2
|
||||
|
||||
+ Currency: Allow mixed letters and numbers in currencies
|
||||
|
||||
+ Deprecate account_tx map/reduce/filterg
|
||||
|
||||
+ Fix: correct requestLedger arguments
|
||||
|
||||
+ Fix: missing subscription on error events for some server methods
|
||||
|
||||
+ Fix: orderbook reset on reconnect
|
||||
|
||||
+ Fix: ripple-lib crashing. Add potential missing error handlers
|
||||
|
||||
|
||||
##0.8.1
|
||||
|
||||
+ Wallet: Add Wallet class that generates wallets
|
||||
|
||||
+ Make npm test runnable in Windows.
|
||||
|
||||
+ Fix several stability issues, see merged PR's for details
|
||||
|
||||
+ Fix bug in Amount.to_human_full()
|
||||
|
||||
+ Fix undefined fee states when connecting to a rippled that is syncing
|
||||
|
||||
|
||||
##0.8.0
|
||||
|
||||
+ Orderbook: Added tracking of offer funds for determining when offers are not funded
|
||||
|
||||
+ Orderbook: Added tests
|
||||
|
||||
+ Orderbook: Update owner funds
|
||||
|
||||
+ Transactions: If transaction errs with `tefALREADY`, wait until all possible submissions err with the same before emitting `error`. Fixes a client "Transaction malformed" bug.
|
||||
|
||||
+ Transactions: Track submissions, don't bother submitting to unconnected servers
|
||||
|
||||
+ Request: `request.request()` now accepts an array of servers as first argument. Servers can be represented with URL, or the server object itself.
|
||||
|
||||
+ Request: `request.broadcast()` now returns the number of servers request was sent to
|
||||
|
||||
+ Server: Acquire host information from server without additional request
|
||||
|
||||
+ Amount: Add a constant for the maximum canonical value that can be expressed as a Ripple value
|
||||
|
||||
+ Amount: Make Constants static fields on the class, instead of a seperate export
|
||||
|
||||
|
||||
##0.7.39
|
||||
|
||||
+ Improvements to multi-server support. Fixed an issue where a server's score was not reset and connections would keep dropping after being connected for a significant amount of time.
|
||||
|
||||
+ Improvements in order book support. Added support for currency pairs with interest bearing currencies. You can request an order book with hex, ISO code or full name for the currency.
|
||||
|
||||
+ Fix value parsing for amount/currency order pairs, e.g. `Amount.from_human("XAU 12345.6789")`
|
||||
|
||||
+ Improved Amount parsing from human readable string given a hex currency, e.g. `Amount.from_human("10 015841551A748AD2C1F76FF6ECB0CCCD00000000")`
|
||||
|
||||
+ Improvements to username normalization in the vault client
|
||||
|
||||
+ Add 2-factor authentication support for vault client
|
||||
|
||||
+ Removed vestiges of Grunt, switched to Gulp
|
||||
|
||||
|
||||
##0.7.37
|
||||
|
||||
+ **Deprecations**
|
||||
|
||||
1. Removed humanistic amount detection in `transaction.payment`. Passing `1XRP` as the payment amount no longer works.
|
||||
2. `remote.setServer` uses full server URL rather than hostname. Example: `remote.setServer('wss://s`.ripple.com:443')`
|
||||
3. Removed constructors for deprecated transaction types from `transaction.js`.
|
||||
4. Removed `invoiceID` option from `transaction.payment`. Instead, use the `transaction.invoiceID` method.
|
||||
5. Removed `transaction.transactionManager` getter.
|
||||
|
||||
+ Improved multi-server support. Servers are now ranked dynamically, and transactions are broadcasted to all connected servers.
|
||||
|
||||
+ Automatically ping connected servers. Client configuration now should contain `ping: <seconds>` to specify the ping interval.
|
||||
|
||||
+ Added `transaction.lastLedger` to specify `LastLedgerSequence`. Setting it this way also ensures that the sequence is not bumped on subsequent requests.
|
||||
|
||||
+ Added optional `remote.accountTx` binary parsing.
|
||||
```js
|
||||
{
|
||||
binary: true,
|
||||
parseBinary: false
|
||||
}
|
||||
```
|
||||
+ Added full currency name support, e.g. `Currency.from_json('XRP').to_human({full_name:'Ripples'})` will return `XRP - Ripples`
|
||||
|
||||
+ Improved interest bearing currency support, e.g. `Currency.from_human('USD - US Dollar (2.5%pa)')`
|
||||
|
||||
+ Improve test coverage
|
||||
|
||||
+ Added blob vault client. The vault client facilitates interaction with ripple's namespace and blob vault or 3rd party blob vaults using ripple's blob vault software (https://github.com/ripple/ripple-blobvault). A list of the available functions can be found at [docs/VAULTCLIENT.md](docs/VAULTCLIENT.md)
|
||||
|
||||
|
||||
##0.7.35
|
||||
|
||||
+ `LastLedgerSequence` is set by default on outgoing transactions. This refers to the last valid ledger index (AKA sequence) for a transaction. By default, this index is set to the current index (at submission time) plus 8. In theory, this allows ripple-lib to deterministically fail a transaction whose submission request timed out, but whose associated server continues to emit ledger_closed events.
|
||||
|
||||
+ Transactions that err with `telINSUF_FEE_P` will be automatically resubmitted. This error indicates that the `Fee` supplied in the transaction submission request was inadquate. Ideally, the `Fee` is tracked by ripple-lib in real-time, and the resubmitted transaction will most likely succeed.
|
||||
|
||||
+ Added Transaction.iff(function(callback) { }). Callback expects first argument to be an Error or null, second argument is a boolean which indicates whether or not to proceed with the transaction submission. If an `iff` function is specified, it will be executed prior to every submission of the transaction (including resubmissions).
|
||||
|
||||
+ Transactions will now emit `presubmit` and `postsubmit` events. They will be emitted before and after a transaction is submitted, respectively.
|
||||
|
||||
+ Added Transaction.summary(). Returns a summary of a transaction in semi-human-readable form. JSON-stringifiable.
|
||||
|
||||
+ Remote.requestAccountTx() with `binary: true` will automatically parse transactions.
|
||||
|
||||
+ Added Remote.requestAccountTx filter, map, and reduce.
|
||||
|
||||
```js
|
||||
remote.requestAccountTx({
|
||||
account: 'retc',
|
||||
ledger_index_min: -1,
|
||||
ledger_index_max: -1,
|
||||
limit: 100,
|
||||
binary: true,
|
||||
|
||||
filter: function(transaction) {
|
||||
return transaction.tx.TransactionType === 'Payment';
|
||||
},
|
||||
|
||||
map: function(transaction) {
|
||||
return Number(transaction.tx.Amount);
|
||||
},
|
||||
|
||||
reduce: function(a, b) {
|
||||
return a + b;
|
||||
},
|
||||
|
||||
pluck: 'transactions'
|
||||
}, console.log)
|
||||
```
|
||||
|
||||
+ Added persistence hooks.
|
||||
|
||||
+ General performance improvements, especially for long-running processes.
|
||||
|
||||
62
LICENSE
62
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2012,2013 OpenCoin, Inc.
|
||||
Copyright (c) 2012,2013,2014 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -12,39 +12,39 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
--------------------------------------
|
||||
|
||||
Some code from Tom Wu:
|
||||
This software is covered under the following copyright:
|
||||
|
||||
/*
|
||||
* Copyright (c) 2003-2005 Tom Wu
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
|
||||
* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
* IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
|
||||
* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
|
||||
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
|
||||
* THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
|
||||
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
* In addition, the following condition applies:
|
||||
*
|
||||
* All redistributions must retain an intact copy of this copyright notice
|
||||
* and disclaimer.
|
||||
*/
|
||||
Copyright (c) 2003-2005 Tom Wu
|
||||
All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
|
||||
WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
|
||||
INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
|
||||
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
|
||||
THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
In addition, the following condition applies:
|
||||
|
||||
All redistributions must retain an intact copy of this copyright notice
|
||||
and disclaimer.
|
||||
|
||||
Address all questions regarding this license to:
|
||||
|
||||
|
||||
177
README.md
177
README.md
@@ -1,123 +1,94 @@
|
||||
Ripple JavaScript Library - ripple-lib
|
||||
======================================
|
||||
#ripple-lib
|
||||
|
||||
This library can connect to the Ripple network via the WebSocket protocol and runs in Node.js as well as in the browser.
|
||||
JavaScript client for [rippled](https://github.com/ripple/rippled)
|
||||
|
||||
* https://ripple.com/wiki/Ripple_JavaScript_library
|
||||
* https://ripple.com
|
||||
* https://ripple.com/wiki
|
||||
[](https://travis-ci.org/ripple/ripple-lib) [](https://coveralls.io/r/ripple/ripple-lib?branch=develop)
|
||||
|
||||
##Initializing a remote connection
|
||||
[](https://www.npmjs.org/package/ripple-lib)
|
||||
|
||||
[ripple-lib.remote](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/remote.js) is responsible for managing connections to rippled servers.
|
||||
###Features
|
||||
|
||||
+ Connect to a rippled server in JavaScript (Node.js or browser)
|
||||
+ Issue [rippled API](https://ripple.com/wiki/JSON_Messages) requests
|
||||
+ Listen to events on the Ripple network (transaction, ledger, etc.)
|
||||
+ Sign and submit transactions to the Ripple network
|
||||
|
||||
###In this file
|
||||
|
||||
1. [Installation](README.md#installation)
|
||||
2. [Quickstart](README.md#quickstart)
|
||||
3. [Running tests](https://github.com/ripple/ripple-lib#running-tests)
|
||||
|
||||
###Additional documentation
|
||||
|
||||
1. [Guides](docs/GUIDES.md)
|
||||
2. [API Reference](docs/REFERENCE.md)
|
||||
3. [Wiki](https://ripple.com/wiki/Ripple_JavaScript_library)
|
||||
|
||||
###Also see
|
||||
|
||||
+ [The Ripple wiki](https://ripple.com/wiki)
|
||||
+ [ripple.com](https://ripple.com)
|
||||
|
||||
##Installation
|
||||
|
||||
**Via npm for Node.js**
|
||||
|
||||
```
|
||||
$ npm install ripple-lib
|
||||
```
|
||||
|
||||
**Via bower (for browser use)**
|
||||
|
||||
```
|
||||
$ bower install ripple
|
||||
```
|
||||
|
||||
See the [bower-ripple repo](https://github.com/ripple/bower-ripple) for additional bower instructions
|
||||
|
||||
|
||||
**Building ripple-lib from github**
|
||||
|
||||
```
|
||||
$ git clone https://github.com/ripple/ripple-lib
|
||||
$ npm install
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
Then use the minified `build/ripple-*-min.js`
|
||||
|
||||
##Quickstart
|
||||
|
||||
`Remote.js` ([remote.js](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/remote.js)) is the point of entry for interacting with rippled
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib with Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
|
||||
/* Loading ripple-lib in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
|
||||
var remote = new Remote({
|
||||
trusted: false,
|
||||
servers: [
|
||||
{
|
||||
host: ''
|
||||
, port: 1111,
|
||||
, secure: true
|
||||
}
|
||||
]
|
||||
// see the API Reference for available options
|
||||
servers: [ 'wss://s1.ripple.com:443' ]
|
||||
});
|
||||
|
||||
remote.connect();
|
||||
```
|
||||
remote.connect(function() {
|
||||
/* remote connected */
|
||||
remote.request('server_info', function(err, info) {
|
||||
|
||||
Once a connection is formed to any of the supplied servers, a `connect` event is emitted, indicating that the remote is ready to begin fulfilling requests. When there are no more connected servers to fulfill requests, a `disconnect` event is emitted. If you send requests before ripple-lib is connected to any servers, requests are deferred until the `connect` event is received.
|
||||
|
||||
```js
|
||||
var remote = new Remote({ /* options */ }).connect();
|
||||
remote.request_server_info(function(err, info) { }); // will defer until connected
|
||||
```
|
||||
|
||||
##Remote functions
|
||||
|
||||
Each remote function returns a `Request` object. is object is an `EventEmitter`. You may listen for success or failure events from each request, or provide a callback. Example:
|
||||
|
||||
```js
|
||||
var request = remote.request_server_info();
|
||||
request.on('success', function(res) {
|
||||
//handle success conditions
|
||||
});
|
||||
request.on('error', function(err) {
|
||||
//handle error conditions
|
||||
});
|
||||
request.request();
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```js
|
||||
remote.request_server_info(function(err, res) {
|
||||
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**request_server_info([callback])**
|
||||
##Running tests
|
||||
|
||||
**request_ledger(ledger, [opts], [callback])**
|
||||
1. Clone the repository
|
||||
|
||||
**request_ledger_hash([callback])**
|
||||
2. `cd` into the repository and install dependencies with `npm install`
|
||||
|
||||
**request_ledger_header([callback])**
|
||||
3. `npm test` or `node_modules/.bin/mocha test/*-test.js`
|
||||
|
||||
**request_ledger_current([callback])**
|
||||
**Generating code coverage**
|
||||
|
||||
**request_ledger_entry(type, [callback])**
|
||||
|
||||
**request_subscribe(streams, [callback])**
|
||||
|
||||
**request_unsubscribe(streams, [callback])**
|
||||
|
||||
**request_transaction_entry(hash, [callback])**
|
||||
|
||||
**request_tx(hash, [callback])**
|
||||
|
||||
**request_account_info(accountID, [callback])**
|
||||
|
||||
**request_account_lines(accountID, account_index, current, [callback])**
|
||||
|
||||
**request_account_offers(accountID, account_index, current, [callback])**
|
||||
|
||||
**request_account_tx(opts, [callback])**
|
||||
|
||||
**request_book_offers(gets, pays, taker, [callback])**
|
||||
|
||||
**request_wallet_accounts(seed, [callback])**
|
||||
|
||||
+ requires trusted **remote
|
||||
|
||||
**request_sign(secret, tx_json, [callback])**
|
||||
|
||||
+ requires trusted **remote
|
||||
|
||||
**request_submit([callback])**
|
||||
|
||||
**request_account_balance(account, current, [callback])**
|
||||
|
||||
**request_account_flags(account, current, [callback])**
|
||||
|
||||
**request_owner_count(account, current, [callback])**
|
||||
|
||||
**request_ripple_balance(account, issuer, currency, current, [callback])**
|
||||
|
||||
**request_ripple_path_find(src_account, dst_account, dst_amount, src_currencies, [callback])**
|
||||
|
||||
**request_unl_list([callback])**
|
||||
|
||||
**request_unl_add(addr, comment, [callback])**
|
||||
|
||||
**request_unl_delete(node, [callback])**
|
||||
|
||||
**request_peers([callback])**
|
||||
|
||||
**request_connect(ip, port, [callback])**
|
||||
|
||||
**transaction()**
|
||||
|
||||
+ returns a [Transaction](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/transaction.js) object
|
||||
ripple-lib uses `istanbul` to generate code coverage. To create a code coverage report, run `npm test --coverage`. The report will be created in `coverage/lcov-report/`.
|
||||
|
||||
44
bench/modpow.js
Normal file
44
bench/modpow.js
Normal file
@@ -0,0 +1,44 @@
|
||||
var Benchmark;
|
||||
try {
|
||||
Benchmark = require('benchmark');
|
||||
} catch (e) {
|
||||
console.error("Please install Benchmark.js: npm install benchmark");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var sjcl = require('../build/sjcl');
|
||||
var jsbn = require('../src/js/jsbn/jsbn');
|
||||
|
||||
var base = "3f70f29d3f3ae354a6d2536ceafba83cfc787cd91e7acd2b6bde05e62beb8295ae18e3f786726f8d034bbc15bf8331df959f59d431736d5f306aaba63dacec279484e39d76db9b527738072af15730e8b9956a64e8e4dbe868f77d1414a8a8b8bf65380a1f008d39c5fabe1a9f8343929342ab7b4f635bdc52532d764701ff3d8072c475c012ff0c59373e8bc423928d99f58c3a6d9f6ab21ee20bc8e8818fc147db09f60c81906f2c6f73dc69725f075853a89f0cd02a30a8dd86b660ccdeffc292f398efb54088c822774445a6afde471f7dd327ef9996296898a5747726ccaeeceeb2e459df98b4128cb5ab8c7cd20c563f960a1aa770f3c81f13f967b6cc";
|
||||
var exponent = "322e393f76a1c22b147e7d193c00c023afb7c1500b006ff1bc1cc8d391fc38bd";
|
||||
var modulus = "c7f1bc1dfb1be82d244aef01228c1409c198894eca9e21430f1669b4aa3864c9f37f3d51b2b4ba1ab9e80f59d267fda1521e88b05117993175e004543c6e3611242f24432ce8efa3b81f0ff660b4f91c5d52f2511a6f38181a7bf9abeef72db056508bbb4eeb5f65f161dd2d5b439655d2ae7081fcc62fdcb281520911d96700c85cdaf12e7d1f15b55ade867240722425198d4ce39019550c4c8a921fc231d3e94297688c2d77cd68ee8fdeda38b7f9a274701fef23b4eaa6c1a9c15b2d77f37634930386fc20ec291be95aed9956801e1c76601b09c413ad915ff03bfdc0b6b233686ae59e8caf11750b509ab4e57ee09202239baee3d6e392d1640185e1cd";
|
||||
var expected = "5b3823974b3eda87286d3f38499de290bd575d8b02f06720acacf3d50950f9ca0ff6b749f3be03913ddca0b291e0b263bdab6c9cb97e4ab47ee9c235ff20931a8ca358726fab93614e2c549594f5c50b1c979b34f840b6d4fc51d6feb2dd072995421d17862cb405e040fc1ed662a3245a1f97bbafa6d1f7f76c7db6a802e3037acdf01ab5053f5da518d6753477193b9c25e1720519dcb9e2f6e70d5786656d356151845a49861dfc40187eff0e85cd18b1f3f3b97c476472edfa090b868b2388edfffecc521c20df8cebb8aacfb3669b020330dd6ea64b2a3067a972b8f249bccc19347eff43893e916f0949bd5789a5cce0f8b7cd87cece909d679345c0d4";
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var jsbnBase = new BigInteger(base, 16);
|
||||
var jsbnExponent = new BigInteger(exponent, 16);
|
||||
var jsbnModulus = new BigInteger(modulus, 16);
|
||||
|
||||
var bn = sjcl.bn;
|
||||
var sjclBase = new bn(base);
|
||||
var sjclExponent = new bn(exponent);
|
||||
var sjclModulus = new bn(modulus);
|
||||
|
||||
var suite = new Benchmark.Suite;
|
||||
|
||||
// add tests
|
||||
suite.add('jsbn#modPow', function() {
|
||||
jsbnBase.modPow(jsbnExponent, jsbnModulus);
|
||||
});
|
||||
suite.add('sjcl#powermodMontgomery', function() {
|
||||
sjclBase.powermodMontgomery(sjclExponent, sjclModulus);
|
||||
});
|
||||
suite.on('cycle', function(event) {
|
||||
console.log(String(event.target));
|
||||
});
|
||||
suite.on('complete', function() {
|
||||
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
|
||||
});
|
||||
// run async
|
||||
console.log("Running benchmark...");
|
||||
suite.run({ 'async': false });
|
||||
47
bin/decode_binary.js
Executable file
47
bin/decode_binary.js
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var SerializedObject = require('../src/js/ripple/serializedobject').SerializedObject;
|
||||
|
||||
var argv = process.argv.slice(2);
|
||||
|
||||
var blob;
|
||||
|
||||
blob = argv.shift();
|
||||
|
||||
if (blob === '-') {
|
||||
read_input(ready);
|
||||
} else {
|
||||
ready();
|
||||
}
|
||||
|
||||
function read_input(callback) {
|
||||
tx_json = '';
|
||||
process.stdin.on('data', function(data) { tx_json += data; });
|
||||
process.stdin.on('end', callback);
|
||||
process.stdin.resume();
|
||||
}
|
||||
|
||||
function ready() {
|
||||
var valid_arguments = blob;
|
||||
|
||||
if (!valid_arguments) {
|
||||
console.error('Invalid arguments\n');
|
||||
print_usage();
|
||||
} else {
|
||||
decode();
|
||||
}
|
||||
}
|
||||
|
||||
function print_usage() {
|
||||
console.log(
|
||||
'Usage: decode_binary.js <hex_blob>\n\n',
|
||||
'Example: decode_binary.js 120000240000000161D6871AFD498D00000000000000000000000000005553440000000000550FC62003E785DC231A1058A05E56E3F09CF4E668400000000000000A732102AE75B908F0A95F740A7BFA96057637E5C2170BC8DAD13B2F7B52AE75FAEBEFCF811450F97A072F1C4357F1AD84566A609479D927C9428314550FC62003E785DC231A1058A05E56E3F09CF4E6'
|
||||
);
|
||||
};
|
||||
|
||||
function decode() {
|
||||
buffer = new SerializedObject(blob);
|
||||
console.log(buffer.to_json());
|
||||
};
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
125
bin/rsign.js
125
bin/rsign.js
@@ -1,65 +1,96 @@
|
||||
#!/usr/bin/node
|
||||
#!/usr/bin/env node
|
||||
|
||||
var Transaction = require('../src/js/ripple/transaction').Transaction;
|
||||
|
||||
var cursor = 2;
|
||||
var argv = process.argv.slice(2);
|
||||
|
||||
var verbose;
|
||||
var secret;
|
||||
var tx_json;
|
||||
|
||||
var usage = function () {
|
||||
if (~argv.indexOf('-v')){
|
||||
argv.splice(argv.indexOf('-v'), 1);
|
||||
verbose = true;
|
||||
}
|
||||
|
||||
secret = argv.shift();
|
||||
tx_json = argv.shift();
|
||||
|
||||
if (tx_json === '-') {
|
||||
read_input(ready);
|
||||
} else {
|
||||
ready();
|
||||
}
|
||||
|
||||
function read_input(callback) {
|
||||
tx_json = '';
|
||||
process.stdin.on('data', function(data) { tx_json += data; });
|
||||
process.stdin.on('end', callback);
|
||||
process.stdin.resume();
|
||||
}
|
||||
|
||||
function ready() {
|
||||
var valid_arguments = secret && tx_json;
|
||||
|
||||
if (!valid_arguments) {
|
||||
console.error('Invalid arguments\n');
|
||||
print_usage();
|
||||
} else {
|
||||
var valid_json = true;
|
||||
|
||||
try {
|
||||
tx_json = JSON.parse(tx_json);
|
||||
} catch(exception) {
|
||||
valid_json = false;
|
||||
}
|
||||
|
||||
if (!valid_json) {
|
||||
console.error('Invalid JSON\n');
|
||||
print_usage();
|
||||
} else {
|
||||
sign_transaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function print_usage() {
|
||||
console.log(
|
||||
"Usage: rsign.js <secret> <json>\n"
|
||||
+ " Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC '{ \"TransactionType\" : \"Payment\", \"Account\" : \"r3P9vH81KBayazSTrQj6S25jW6kDb779Gi\", \"Destination\" : \"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV\", \"Amount\" : \"200000000\", \"Fee\" : \"10\", \"Sequence\" : \"1\" }'"
|
||||
'Usage: rsign.js <secret> <json>\n\n',
|
||||
'Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC','\''+
|
||||
JSON.stringify({
|
||||
TransactionType: 'Payment',
|
||||
Account: 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi',
|
||||
Destination: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV',
|
||||
Amount: '200000000',
|
||||
Fee: '10',
|
||||
Sequence: 1
|
||||
})+'\''
|
||||
);
|
||||
};
|
||||
|
||||
if (process.argv.length > cursor && process.argv[cursor] === "-v")
|
||||
{
|
||||
verbose = true;
|
||||
cursor++;
|
||||
}
|
||||
function sign_transaction() {
|
||||
var tx = new Transaction();
|
||||
|
||||
if (process.argv.length > cursor)
|
||||
{
|
||||
secret = process.argv[cursor++];
|
||||
}
|
||||
tx.tx_json = tx_json;
|
||||
tx._secret = secret;
|
||||
tx.complete();
|
||||
|
||||
if (process.argv.length > cursor)
|
||||
{
|
||||
tx_json = JSON.parse(process.argv[cursor++]);
|
||||
}
|
||||
var unsigned_blob = tx.serialize().to_hex();
|
||||
var unsigned_hash = tx.signingHash();
|
||||
tx.sign();
|
||||
|
||||
if (process.argv.length !== cursor || !secret || !tx_json)
|
||||
{
|
||||
usage();
|
||||
}
|
||||
else
|
||||
{
|
||||
var tx = new Transaction();
|
||||
if (verbose) {
|
||||
var sim = { };
|
||||
|
||||
tx.tx_json = tx_json;
|
||||
tx._secret = secret;
|
||||
tx.complete();
|
||||
sim.tx_blob = tx.serialize().to_hex();
|
||||
sim.tx_json = tx.tx_json;
|
||||
sim.tx_signing_hash = unsigned_hash;
|
||||
sim.tx_unsigned = unsigned_blob;
|
||||
|
||||
var unsigned = tx.serialize().to_hex();
|
||||
tx.sign();
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
var sim = {};
|
||||
|
||||
sim.tx_blob = tx.serialize().to_hex();
|
||||
sim.tx_json = tx.tx_json;
|
||||
sim.tx_signing_hash = tx.signing_hash().to_hex();
|
||||
sim.tx_unsigned = unsigned;
|
||||
|
||||
console.log(JSON.stringify(sim, undefined, 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(tx.serialize().to_hex());
|
||||
}
|
||||
}
|
||||
console.log(JSON.stringify(sim, null, 2));
|
||||
} else {
|
||||
console.log(tx.serialize().to_hex());
|
||||
}
|
||||
};
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
26
bin/validate_address.js
Executable file
26
bin/validate_address.js
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var UInt160 = require('../').UInt160;
|
||||
var address = process.argv[2];
|
||||
|
||||
if (address === '-') {
|
||||
readInput(validateAddress);
|
||||
} else {
|
||||
validateAddress(address);
|
||||
}
|
||||
|
||||
function readInput(callback) {
|
||||
var result = '';
|
||||
process.stdin.resume();
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', function(data) {
|
||||
result += data;
|
||||
});
|
||||
process.stdin.on('end', function() {
|
||||
callback(result);
|
||||
});
|
||||
};
|
||||
|
||||
function validateAddress(address) {
|
||||
process.stdout.write((UInt160.is_valid(address.trim()) ? '0' : '1') + '\r\n');
|
||||
};
|
||||
206
docs/GUIDES.md
Normal file
206
docs/GUIDES.md
Normal file
@@ -0,0 +1,206 @@
|
||||
#Guides
|
||||
|
||||
This file provides step-by-step walkthroughs for some of the most common usages of `ripple-lib`.
|
||||
|
||||
###In this document
|
||||
|
||||
1. [Connecting to the Ripple network with `Remote`](GUIDES.md#connecting-to-the-ripple-network)
|
||||
2. [Using `Remote` functions and `Request` objects](GUIDES.md#sending-rippled-API-requests)
|
||||
3. [Listening to the network](GUIDES.md#listening-to-the-network)
|
||||
4. [Submitting a payment to the network](GUIDES.md#submitting-a-payment-to-the-network)
|
||||
* [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees)
|
||||
5. [Submitting a trade offer to the network](GUIDES.md#submitting-a-trade-offer-to-the-network)
|
||||
|
||||
###Also see
|
||||
|
||||
1. [The ripple-lib README](../README.md)
|
||||
2. [The ripple-lib API Reference](REFERENCE.md)
|
||||
|
||||
##Generating a new Ripple Wallet
|
||||
|
||||
```js
|
||||
var Wallet = require('ripple-lib').Wallet;
|
||||
|
||||
var wallet = Wallet.generate();
|
||||
console.log(wallet);
|
||||
// { address: 'rEf4sbVobiiDGExrNj2PkNHGMA8eS6jWh3',
|
||||
// secret: 'shFh4a38EZpEdZxrLifEnVPAoBRce' }
|
||||
```
|
||||
|
||||
##Connecting to the Ripple network
|
||||
|
||||
1. [Get ripple-lib](README.md#getting-ripple-lib)
|
||||
2. Load the ripple-lib module into a Node.js file or webpage:
|
||||
```js
|
||||
/* Loading ripple-lib with Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
|
||||
/* Loading ripple-lib in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
```
|
||||
3. Create a new `Remote` and connect to the network:
|
||||
```js
|
||||
var remote = new Remote({options});
|
||||
|
||||
remote.connect(function() {
|
||||
/* remote connected, use some remote functions here */
|
||||
});
|
||||
```
|
||||
__NOTE:__ See the API Reference for available [`Remote` options](REFERENCE.md#1-remote-options)
|
||||
|
||||
4. You're connected! Read on to see what to do now.
|
||||
|
||||
|
||||
##Sending rippled API requests
|
||||
|
||||
`Remote` contains functions for constructing a `Request` object.
|
||||
|
||||
A `Request` is an `EventEmitter` so you can listen for success or failure events -- or, instead, you can provide a callback.
|
||||
|
||||
Here is an example, using [request_server_info](https://ripple.com/wiki/JSON_Messages#server_info).
|
||||
|
||||
+ Constructing a `Request` with event listeners
|
||||
```js
|
||||
var request = remote.request('server_info');
|
||||
|
||||
request.on('success', function onSuccess(res) {
|
||||
//handle success
|
||||
});
|
||||
|
||||
request.on('error', function onError(err) {
|
||||
//handle error
|
||||
});
|
||||
|
||||
request.request();
|
||||
```
|
||||
|
||||
+ Using a callback:
|
||||
```js
|
||||
remote.request('server_info', function(err, res) {
|
||||
if (err) {
|
||||
//handle error
|
||||
} else {
|
||||
//handle success
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
__NOTE:__ See the API Reference for available [`Remote` functions](REFERENCE.md#2-remote-functions)
|
||||
|
||||
|
||||
##Listening to the network
|
||||
|
||||
See the [wiki](https://ripple.com/wiki/JSON_Messages#subscribe) for details on subscription requests.
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib with Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
|
||||
/* Loading ripple-lib in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
|
||||
var remote = new Remote({options});
|
||||
|
||||
remote.connect(function() {
|
||||
var request = remote.request('subscribe');
|
||||
|
||||
request.addStream('ledger'); //remote will emit `ledger_closed`
|
||||
request.addStream('transactions'); //remote will emit `transaction`
|
||||
|
||||
request.on('ledger_closed', function onLedgerClosed(ledgerData) {
|
||||
//handle ledger
|
||||
});
|
||||
|
||||
request.on('transaction', function onTransacstion(transaction) {
|
||||
//handle transaction
|
||||
});
|
||||
|
||||
request.request(function(err) {
|
||||
if (err) {
|
||||
} else {
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
* https://ripple.com/wiki/RPC_API#transactions_stream_messages
|
||||
* https://ripple.com/wiki/RPC_API#ledger_stream_messages
|
||||
|
||||
##Submitting a payment to the network
|
||||
|
||||
Submitting a payment transaction to the Ripple network involves connecting to a `Remote`, creating a transaction, signing it with the user's secret, and submitting it to the `rippled` server. Note that the `Amount` module is used to convert human-readable amounts like '1XRP' or '10.50USD' to the type of Amount object used by the Ripple network.
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
var Amount = require('ripple-lib').Amount;
|
||||
|
||||
/* Loading ripple-lib Remote and Amount modules in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
// var Amount = ripple.Amount;
|
||||
|
||||
var MY_ADDRESS = 'rrrMyAddress';
|
||||
var MY_SECRET = 'secret';
|
||||
var RECIPIENT = 'rrrRecipient';
|
||||
var AMOUNT = Amount.from_human('1XRP');
|
||||
|
||||
var remote = new Remote({ /* Remote options */ });
|
||||
|
||||
remote.connect(function() {
|
||||
remote.setSecret(MY_ADDRESS, MY_SECRET);
|
||||
|
||||
var transaction = remote.createTransaction('Payment', {
|
||||
account: MY_ADDRESS,
|
||||
destination: RECIPIENT,
|
||||
amount: AMOUNT
|
||||
});
|
||||
|
||||
transaction.submit(function(err, res) {
|
||||
/* handle submission errors / success */
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
###A note on transaction fees
|
||||
|
||||
A full description of network transaction fees can be found on the [Ripple Wiki](https://ripple.com/wiki/Transaction_Fee).
|
||||
|
||||
In short, transaction fees are very small amounts (on the order of ~10) of [XRP drops](https://ripple.com/wiki/Ripple_credits#Notes_on_drops) spent and destroyed with every transaction. They are largely used to account for network load and prevent spam. With `ripple-lib`, transaction fees are calculated locally by default and the fee you are willing to pay is submitted along with your transaction.
|
||||
|
||||
Since the fee required for a transaction may change between the time when the original fee was calculated and the time when the transaction is submitted, it is wise to use the [`fee_cushion`](REFERENCE.md#1-remote-options) to ensure that the transaction will go through. For example, suppose the original fee calculated for a transaction was 10 XRP drops but at the instant the transaction is submitted the server is experiencing a higher load and it has raised its minimum fee to 12 XRP drops. Without a `fee_cusion`, this transaction would not be processed by the server, but with a `fee_cusion` of, say, 1.5 it would be processed and you would just pay the 2 extra XRP drops.
|
||||
|
||||
The [`max_fee`](REFERENCE.md#1-remote-options) option can be used to avoid submitting a transaction to a server that is charging unreasonably high fees.
|
||||
|
||||
|
||||
##4. Submitting a trade offer to the network
|
||||
|
||||
Submitting a trade offer to the network is similar to submitting a payment transaction. Here is an example for a trade that expires in 24 hours where you are offering to sell 1 USD in exchange for 100 XRP:
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
var Amount = require('ripple-lib').Amount;
|
||||
|
||||
/* Loading ripple-lib Remote and Amount modules in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
// var Amount = ripple.Amount;
|
||||
|
||||
var MY_ADDRESS = 'rrrMyAddress';
|
||||
var MY_SECRET = 'secret';
|
||||
var GATEWAY = 'rrrGateWay';
|
||||
|
||||
var remote = new Remote({ /* Remote options */ });
|
||||
|
||||
remote.connect(function() {
|
||||
remote.setSecret(MY_ADDRESS, MY_SECRET);
|
||||
|
||||
var transaction = remote.createTransaction('OfferCreate', {
|
||||
account: MY_ADDRESS,
|
||||
taker_pays: '1',
|
||||
taker_gets: '1/USD/' + GATEWAY
|
||||
});
|
||||
|
||||
transaction.submit(function(err, res) {
|
||||
/* handle submission errors / success */
|
||||
});
|
||||
});
|
||||
```
|
||||
302
docs/REFERENCE.md
Normal file
302
docs/REFERENCE.md
Normal file
@@ -0,0 +1,302 @@
|
||||
#API Reference
|
||||
|
||||
__(More examples coming soon!)__
|
||||
|
||||
###In this document:
|
||||
|
||||
1. [`Remote` options](REFERENCE.md#remote-options)
|
||||
2. [`Request` constructors](REFERENCE.md#request-constructor-functions)
|
||||
+ [Server requests](REFERENCE.md#server-requests)
|
||||
+ [Ledger requests](REFERENCE.md#ledger-requests)
|
||||
+ [Transaction requests](REFERENCE.md#transaction-requests)
|
||||
+ [Account requests](REFERENCE.md#account-requests)
|
||||
+ [Orderbook requests](REFERENCE.md#orderbook-requests)
|
||||
+ [Transaction requests](REFERENCE.md#transaction-requests)
|
||||
3. [`Transaction` constructors](REFERENCE.md#transaction-constructors)
|
||||
+ [Transaction events](REFERENCE.md#transaction-events)
|
||||
|
||||
###Also see:
|
||||
|
||||
1. [The ripple-lib README](../README.md)
|
||||
2. [The ripple-lib GUIDES](GUIDES.md)
|
||||
|
||||
#Remote options
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib with Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
|
||||
/* Loading ripple-lib in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
|
||||
var options = { };
|
||||
|
||||
var remote = new Remote(options);
|
||||
```
|
||||
|
||||
A new `Remote` can be created with the following options:
|
||||
|
||||
+ `trace` *boolean default: false* Log all of the events emitted
|
||||
+ `max_listeners` *number default: 0* Set maxListeners for servers
|
||||
+ `trusted` *boolean default: false*, if remote is trusted (boolean)
|
||||
+ `local_signing` *boolean default: true*
|
||||
+ `local_fee` *boolean default: true* Set whether the transaction fee range will be set locally, see [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees))
|
||||
+ `fee_cushion` *number default: 1.2* Extra fee multiplier to account for async fee changes, see [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees))
|
||||
+ `max_fee` *number default: Infinity* Maximum acceptable transaction fee, see [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees)
|
||||
+ `servers` *array* Array of server objects of the following form:
|
||||
|
||||
```js
|
||||
{
|
||||
host: <string>,
|
||||
port: <number>,
|
||||
secure: <boolean>
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
'wss://host:port'
|
||||
```
|
||||
|
||||
#Request constructor functions
|
||||
|
||||
##Server requests
|
||||
|
||||
**[server_info([callback])](https://ripple.com/wiki/JSON_Messages#server_info)**
|
||||
|
||||
Returns information about the state of the server. If you are connected to multiple servers and want to select by a particular host, use `request.setServer`. Example:
|
||||
|
||||
```js
|
||||
var request = remote.request('server_info');
|
||||
|
||||
request.setServer('wss://s1.ripple.com');
|
||||
|
||||
request.request(function(err, res) {
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
**[unl_list([callback])](https://ripple.com/wiki/JSON_Messages#unl_list)**
|
||||
|
||||
**[unl_add(addr, comment, [callback])](https://ripple.com/wiki/JSON_Messages#unl_add)**
|
||||
|
||||
**[unl_delete(node, [callback])](https://ripple.com/wiki/JSON_Messages#unl_delete)**
|
||||
|
||||
**[requestPeers([callback])](https://ripple.com/wiki/JSON_Messages#peers)**
|
||||
|
||||
|
||||
**[connect(ip, port, [callback])](https://ripple.com/wiki/JSON_Messages#connect)**
|
||||
|
||||
##Ledger requests
|
||||
|
||||
**[ledger(ledger, [opts], [callback])](https://ripple.com/wiki/JSON_Messages#ledger)**
|
||||
|
||||
**ledger_header([callback])**
|
||||
|
||||
**[ledger_current([callback])](https://ripple.com/wiki/JSON_Messages#ledger_current)**
|
||||
|
||||
**[ledger_entry(type, [callback])](https://ripple.com/wiki/JSON_Messages#ledger_entry)**
|
||||
|
||||
**[subscribe([streams], [callback])](https://ripple.com/wiki/JSON_Messages#subscribe)**
|
||||
|
||||
Start receiving selected streams from the server.
|
||||
|
||||
**[unsubscribe([streams], [callback])](https://ripple.com/wiki/JSON_Messages#unsubscribe)**
|
||||
|
||||
Stop receiving selected streams from the server.
|
||||
|
||||
##Account requests
|
||||
|
||||
**[account_info(account, [callback])](https://ripple.com/wiki/JSON_Messages#account_info)**
|
||||
|
||||
Return information about the specified account.
|
||||
|
||||
```
|
||||
{
|
||||
ledger_current_index: <number>,
|
||||
account_data: {
|
||||
Account: <string>,
|
||||
Balance: <number>,
|
||||
Flags: <number>,
|
||||
LedgerEntryType: <string>,
|
||||
OwnerCount: <number>,
|
||||
PreviousTxnID: <string>,
|
||||
PreviousTxnLgrSeq: <number>,
|
||||
Sequence: <number> ,
|
||||
index: <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**[account_lines(accountID, [account_index], [ledger], [callback])](https://ripple.com/wiki/JSON_Messages#account_lines)**
|
||||
|
||||
**[account_offers(accountID, [account_index], [ledger], [callback])](https://ripple.com/wiki/JSON_Messages#account_offers)**
|
||||
|
||||
Return the specified account's outstanding offers.
|
||||
|
||||
**[account_tx(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_tx)**
|
||||
|
||||
Fetch a list of transactions that applied to this account.
|
||||
|
||||
Options:
|
||||
|
||||
+ `account`
|
||||
+ `ledger_index_min`
|
||||
+ `ledger_index_max`
|
||||
+ `binary` *false*
|
||||
+ `count` *false*
|
||||
+ `descending` *false*
|
||||
+ `offset` *0*
|
||||
+ `limit`
|
||||
+ `forward` *false*
|
||||
+ `fwd_marker`
|
||||
+ `rev_marker`
|
||||
|
||||
**[wallet_accounts(seed, [callback])](https://ripple.com/wiki/JSON_Messages#wallet_accounts)**
|
||||
|
||||
Return a list of accounts for a wallet. *Requires trusted remote*
|
||||
|
||||
**account_balance(account, [ledger], [callback])**
|
||||
|
||||
Get the balance for an account. Returns an [Amount](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/amount.js) object.
|
||||
|
||||
**account_flags(account, [ledger], [callback])**
|
||||
|
||||
Return the flags for an account.
|
||||
|
||||
**owner_count(account, [ledger], [callback])**
|
||||
|
||||
Return the owner count for an account.
|
||||
|
||||
**ripple_balance(account, issuer, currency, [ledger], [callback])**
|
||||
|
||||
Return a request to get a ripple balance
|
||||
|
||||
##Orderbook requests
|
||||
|
||||
**[book_offers(options, [callback])](https://ripple.com/wiki/JSON_Messages#book_offers)**
|
||||
|
||||
Return the offers for an order book, also called a *snapshot*
|
||||
|
||||
```js
|
||||
var request = remote.request('book_offers', {
|
||||
taker_gets: {
|
||||
'currency':'XRP'
|
||||
},
|
||||
taker_pays: {
|
||||
'currency':'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
}
|
||||
});
|
||||
|
||||
request.request(function(err, offers) {
|
||||
//handle offers
|
||||
});
|
||||
```
|
||||
|
||||
##Transaction requests
|
||||
|
||||
**[transaction_entry(hash, [ledger_hash], [callback])](https://ripple.com/wiki/JSON_Messages#transaction_entry)**
|
||||
|
||||
Searches a particular ledger for a transaction hash. Default ledger is the open ledger.
|
||||
|
||||
**[tx(hash, [callback])](https://ripple.com/wiki/JSON_Messages#tx)**
|
||||
|
||||
Searches ledger history for validated transaction hashes.
|
||||
|
||||
**[sign(secret, tx_json, [callback])](https://ripple.com/wiki/JSON_Messages#sign)**
|
||||
|
||||
Sign a transaction. *Requires trusted remote*
|
||||
|
||||
**[submit([callback])](https://ripple.com/wiki/JSON_Messages#submit)**
|
||||
|
||||
Submit a transaction to the network. This command is used internally to submit transactions with a greater degree of reliability. See [Submitting a payment to the network](GUIDES.md#3-submitting-a-payment-to-the-network) for details.
|
||||
|
||||
**[ripple_path_find(src_account, dst_account, dst_amount, src_currencies, [callback])](https://ripple.com/wiki/JSON_Messages#path_find)**
|
||||
|
||||
#Transaction constructors
|
||||
|
||||
Use `remote.createTransaction('TransactionType', [options])` to construct a transaction. To submit, use `transaction.submit([callback])`.
|
||||
|
||||
**Payment**
|
||||
|
||||
```js
|
||||
var transaction = remote.createTransaction('Payment', {
|
||||
account: MY_ADDRESS,
|
||||
destination: DEST_ADDRESS,
|
||||
amount: AMOUNT
|
||||
});
|
||||
```
|
||||
|
||||
**AccountSet**
|
||||
|
||||
```js
|
||||
var transaction = remote.createTransaction('AccountSet', {
|
||||
account: MY_ADDRESS,
|
||||
set: 'RequireDest',
|
||||
clear: 'RequireAuth'
|
||||
});
|
||||
```
|
||||
|
||||
**TrustSet**
|
||||
|
||||
```js
|
||||
var transaction = remote.createTransaction('TrustSet', {
|
||||
account: MY_ADDRESS,
|
||||
limit: '1/USD/rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
});
|
||||
```
|
||||
|
||||
**OfferCreate**
|
||||
|
||||
```js
|
||||
var transaction = remote.createTransaction('OfferCreate', {
|
||||
account: MY_ADDRESS,
|
||||
taker_pays: '1',
|
||||
taker_gets: '1/USD/rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
});
|
||||
```
|
||||
|
||||
##Transaction events
|
||||
|
||||
[Transaction](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/transaction.js) objects are EventEmitters. They may emit the following events.
|
||||
|
||||
+ `final` Transaction has erred or succeeded. This event indicates that the transaction has finished processing.
|
||||
+ `error` Transaction has erred. This event is a final state.
|
||||
+ `success` Transaction succeeded. This event is a final state.
|
||||
+ `presubmit` Immediately before transaction is submitted
|
||||
+ `postsubmit` Immediately after transaction is submitted
|
||||
+ `submitted` Transaction has been submitted to the network. The submission may result in a remote error or success.
|
||||
+ `resubmitted` Transaction is beginning resubmission.
|
||||
+ `proposed` Transaction has been submitted *successfully* to the network. The transaction at this point is awaiting validation in a ledger.
|
||||
+ `timeout` Transaction submission timed out. The transaction will be resubmitted.
|
||||
+ `fee_adjusted` Transaction fee has been adjusted during its pending state. The transaction fee will only be adjusted if the remote is configured for local fees, which it is by default.
|
||||
+ `abort` Transaction has been aborted. Transactions are only aborted by manual calls to `#abort`.
|
||||
+ `missing` Four ledgers have closed without detecting validated transaction
|
||||
+ `lost` Eight ledgers have closed without detecting validated transaction. Consider the transaction lost and err/finalize.
|
||||
|
||||
##Complete payment example
|
||||
|
||||
```js
|
||||
remote.setSecret(MY_ADDRESS, MY_SECRET);
|
||||
|
||||
var transaction = remote.createTransaction('Payment', {
|
||||
account: MY_ADDRESS,
|
||||
destination: DEST_ADDRESS,
|
||||
amount: AMOUNT
|
||||
});
|
||||
|
||||
transaction.on('resubmitted', function() {
|
||||
// initial submission failed, resubmitting
|
||||
});
|
||||
|
||||
transaction.submit(function(err, res) {
|
||||
// submission has finalized with either an error or success.
|
||||
// the transaction will not be retried after this point
|
||||
});
|
||||
```
|
||||
|
||||
#Amount objects
|
||||
|
||||
Coming Soon
|
||||
168
docs/VAULTCLIENT.md
Normal file
168
docs/VAULTCLIENT.md
Normal file
@@ -0,0 +1,168 @@
|
||||
ripple-vault-client
|
||||
===================
|
||||
|
||||
A javascript / http client to interact with Ripple Vault servers.
|
||||
|
||||
The purpose of this tool is to enable applications in any javascript
|
||||
environment to login with the ripple vault and access the decrypted
|
||||
data stored using credentials originally obtained at ripple.com
|
||||
|
||||
|
||||
## Vault Client Usage
|
||||
|
||||
vaultClient = new ripple.VaultClient(domain);
|
||||
|
||||
vaultClient.getAuthInfo(username, callback);
|
||||
|
||||
vaultClient.getRippleName(address, url, callback);
|
||||
|
||||
vaultClient.exists(username, callback);
|
||||
|
||||
|
||||
|
||||
vaultClient.login(username, password, callback);
|
||||
|
||||
vaultClient.relogin(id, cryptKey, callback);
|
||||
|
||||
vaultClient.unlock(username, password, encryptSecret, callback);
|
||||
|
||||
vaultClient.loginAndUnlock(username, password, callback);
|
||||
|
||||
|
||||
|
||||
vaultClient.register(options, callback);
|
||||
|
||||
vaultClient.deleteBlob(options, callback);
|
||||
|
||||
vaultClient.recoverBlob(options, callback);
|
||||
|
||||
vaultClient.rename(options, callback);
|
||||
|
||||
vaultClient.changePassword(options, callback);
|
||||
|
||||
vaultClient.verify(username, token, callback);
|
||||
|
||||
vaultClient.resendEmail(options, callback);
|
||||
|
||||
vaultClient.updateProfile(options, fn);
|
||||
|
||||
|
||||
# Blob Methods
|
||||
|
||||
blob.encrypt();
|
||||
|
||||
blob.decrypt(encryptedBlob);
|
||||
|
||||
blob.encryptSecret(encryptionKey);
|
||||
|
||||
blob.decryptSecret(encryptionKey, secret);
|
||||
|
||||
blob.set(pointer, value, callback);
|
||||
|
||||
blob.unset(pointer, callback);
|
||||
|
||||
blob.extend(pointer, value, callback);
|
||||
|
||||
blob.unshift(pointer, value, callback);
|
||||
|
||||
blob.filter(pointer, field, value, subcommands, callback);
|
||||
|
||||
|
||||
## Identity Vault
|
||||
|
||||
The identity vault stores identity information inside the encrypted
|
||||
blob vault. The identity fields can be additionally encrypted with the
|
||||
unlock key, that encrypts the secret, for added security. Methods are
|
||||
accessed from the 'identity' property of the blob object.
|
||||
|
||||
|
||||
# Identity fields
|
||||
+ name
|
||||
+ entityType (individual, corporation, organization)
|
||||
+ email
|
||||
+ phone
|
||||
+ address
|
||||
+ contact
|
||||
+ line1
|
||||
+ line2
|
||||
+ city
|
||||
+ postalCode
|
||||
+ region - state/province/region
|
||||
+ country
|
||||
+ nationalID
|
||||
+ number
|
||||
+ type (ssn, taxID, passport, driversLicense, other)
|
||||
+ country - issuing country
|
||||
+ birthday
|
||||
+ birthplace
|
||||
|
||||
|
||||
# Identity Methods
|
||||
|
||||
blob.identity.set(pointer, key, value, callback);
|
||||
|
||||
blob.identity.unset(pointer, key, callback);
|
||||
|
||||
blob.identity.get(pointer, key);
|
||||
|
||||
blob.identity.getAll(key);
|
||||
|
||||
blob.identity.getFullAddress(key); //get text string of full address
|
||||
|
||||
|
||||
## Spec Tests
|
||||
|
||||
Run `npm test` to test the high-level behavior specs
|
||||
|
||||
Ripple Txt
|
||||
✓ should get the content of a ripple.txt file from a given domain
|
||||
✓ should get currencies from a ripple.txt file for a given domain
|
||||
✓ should get the domain from a given url
|
||||
|
||||
AuthInfo
|
||||
✓ should get auth info
|
||||
|
||||
VaultClient
|
||||
#initialization
|
||||
✓ should be initialized with a domain
|
||||
✓ should default to ripple.com without a domain
|
||||
#exists
|
||||
✓ should determine if a username exists on the domain
|
||||
#login
|
||||
✓ with username and password should retrive the blob, crypt key, and id
|
||||
#relogin
|
||||
✓ should retrieve the decrypted blob with blob vault url, id, and crypt key
|
||||
#unlock
|
||||
✓ should access the wallet secret using encryption secret, username and password
|
||||
#loginAndUnlock
|
||||
✓ should get the decrypted blob and decrypted secret given name and password
|
||||
#register
|
||||
✓ should create a new blob
|
||||
#deleteBlob
|
||||
✓ should remove an existing blob
|
||||
#updateProfile
|
||||
✓ should update profile parameters associated with a blob
|
||||
|
||||
Blob
|
||||
✓ #set
|
||||
✓ #extend
|
||||
✓ #unset
|
||||
✓ #unshift
|
||||
✓ #filter
|
||||
✓ #consolidate
|
||||
#rename
|
||||
✓ should change the username of a blob
|
||||
#changePassword
|
||||
✓ should change the password and keys of a blob
|
||||
#recoverBlob
|
||||
✓ should recover the blob given a username and secret
|
||||
#verifyEmail
|
||||
✓ should verify an email given a username and token
|
||||
#resendVerifcationEmail
|
||||
✓ should resend a verification given options
|
||||
identity
|
||||
✓ #identity_set
|
||||
✓ #identity_get
|
||||
✓ #identity_getAll
|
||||
✓ #identity_getFullAddress
|
||||
✓ #identity_unset
|
||||
142
npm-shrinkwrap.json
generated
Normal file
142
npm-shrinkwrap.json
generated
Normal file
@@ -0,0 +1,142 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.9.0-rc5",
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "0.8.0",
|
||||
"from": "async@>=0.8.0 <0.9.0"
|
||||
},
|
||||
"extend": {
|
||||
"version": "1.2.1",
|
||||
"from": "extend@>=1.2.1 <1.3.0"
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "2.5.0",
|
||||
"from": "lru-cache@>=2.5.0 <2.6.0"
|
||||
},
|
||||
"ripple-wallet-generator": {
|
||||
"version": "1.0.1",
|
||||
"from": "ripple-wallet-generator@1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ripple-wallet-generator/-/ripple-wallet-generator-1.0.1.tgz"
|
||||
},
|
||||
"superagent": {
|
||||
"version": "0.18.2",
|
||||
"from": "superagent@>=0.18.0 <0.19.0",
|
||||
"dependencies": {
|
||||
"qs": {
|
||||
"version": "0.6.6",
|
||||
"from": "qs@0.6.6",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz"
|
||||
},
|
||||
"formidable": {
|
||||
"version": "1.0.14",
|
||||
"from": "formidable@1.0.14",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz"
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.2.11",
|
||||
"from": "mime@1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.1.2",
|
||||
"from": "component-emitter@1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz"
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.0.1",
|
||||
"from": "methods@1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.0.1.tgz"
|
||||
},
|
||||
"cookiejar": {
|
||||
"version": "2.0.1",
|
||||
"from": "cookiejar@2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.0.1.tgz"
|
||||
},
|
||||
"debug": {
|
||||
"version": "1.0.4",
|
||||
"from": "debug@>=1.0.1 <1.1.0",
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "0.6.2",
|
||||
"from": "ms@0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"reduce-component": {
|
||||
"version": "1.0.1",
|
||||
"from": "reduce-component@1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/reduce-component/-/reduce-component-1.0.1.tgz"
|
||||
},
|
||||
"form-data": {
|
||||
"version": "0.1.3",
|
||||
"from": "form-data@0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.3.tgz",
|
||||
"dependencies": {
|
||||
"combined-stream": {
|
||||
"version": "0.0.5",
|
||||
"from": "combined-stream@>=0.0.4 <0.1.0",
|
||||
"dependencies": {
|
||||
"delayed-stream": {
|
||||
"version": "0.0.5",
|
||||
"from": "delayed-stream@0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"async": {
|
||||
"version": "0.9.0",
|
||||
"from": "async@>=0.9.0 <0.10.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.0.27-1",
|
||||
"from": "readable-stream@1.0.27-1",
|
||||
"dependencies": {
|
||||
"core-util-is": {
|
||||
"version": "1.0.1",
|
||||
"from": "core-util-is@>=1.0.0 <1.1.0"
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"from": "isarray@0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"from": "string_decoder@>=0.10.0 <0.11.0"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1",
|
||||
"from": "inherits@>=2.0.1 <2.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "0.4.32",
|
||||
"from": "ws@>=0.4.31 <0.5.0",
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.1.0",
|
||||
"from": "commander@>=2.1.0 <2.2.0"
|
||||
},
|
||||
"nan": {
|
||||
"version": "1.0.0",
|
||||
"from": "nan@>=1.0.0 <1.1.0"
|
||||
},
|
||||
"tinycolor": {
|
||||
"version": "0.0.1",
|
||||
"from": "tinycolor@>=0.0.0 <1.0.0"
|
||||
},
|
||||
"options": {
|
||||
"version": "0.0.6",
|
||||
"from": "options@>=0.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
package.json
52
package.json
@@ -1,39 +1,51 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.7.19",
|
||||
"description": "Ripple JavaScript client library",
|
||||
"version": "0.9.2-rc5",
|
||||
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
|
||||
"files": [
|
||||
"src/js/ripple/*.js",
|
||||
"build/sjcl.js",
|
||||
"bin/rsign.js"
|
||||
"src/js/*",
|
||||
"bin/*",
|
||||
"build/*",
|
||||
"test/*",
|
||||
"Makefile",
|
||||
"Gulpfile.js"
|
||||
],
|
||||
"main": "src/js/ripple",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "~0.2.9",
|
||||
"ws": "~0.4.27",
|
||||
"extend": "~1.1.3",
|
||||
"simple-jsonrpc": "~0.0.2",
|
||||
"jshint-loader": "~0.5.0"
|
||||
"async": "~0.8.0",
|
||||
"ws": "~0.4.31",
|
||||
"extend": "~1.2.1",
|
||||
"lru-cache": "~2.5.0",
|
||||
"superagent": "^0.18.0",
|
||||
"ripple-wallet-generator": "1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-cli": "~0.1.9",
|
||||
"grunt-contrib-concat": "~0.3.0",
|
||||
"grunt-contrib-watch": "~0.4.4",
|
||||
"grunt-webpack": "~0.10.5",
|
||||
"grunt-dox": "~0.5.0",
|
||||
"buster": "~0.6.12"
|
||||
"mocha": "~1.14.0",
|
||||
"gulp": "~3.6.2",
|
||||
"gulp-concat": "~2.2.0",
|
||||
"gulp-jshint": "~1.5.5",
|
||||
"gulp-uglify": "~0.3.0",
|
||||
"gulp-rename": "~1.2.0",
|
||||
"gulp-bump": "~0.1.10",
|
||||
"webpack": "~1.1.11",
|
||||
"map-stream": "~0.1.0",
|
||||
"istanbul": "~0.2.10",
|
||||
"coveralls": "~2.10.0",
|
||||
"nock": "^0.34.1",
|
||||
"yargs": "~1.3.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/buster/bin/buster-test",
|
||||
"build": "node_modules/.bin/grunt"
|
||||
"build": "node_modules/.bin/gulp",
|
||||
"pretest": "node_modules/.bin/gulp concat-sjcl",
|
||||
"test": "./node_modules/.bin/istanbul test -x build/sjcl.js -x src/js/jsbn/* ./node_modules/mocha/bin/_mocha -- --reporter spec test/*-test.js",
|
||||
"coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/rippleFoundation/ripple-lib.git"
|
||||
"url": "git://github.com/ripple/ripple-lib.git"
|
||||
},
|
||||
"readmeFilename": "README.md",
|
||||
"engines": {
|
||||
|
||||
43
scripts/publish
Normal file
43
scripts/publish
Normal file
@@ -0,0 +1,43 @@
|
||||
echo "PUBLISH"
|
||||
|
||||
function exit_on_error {
|
||||
res=$?
|
||||
[[ ${res:-99} -eq 0 ]] || exit $res
|
||||
}
|
||||
|
||||
rm -rf build
|
||||
|
||||
npm install
|
||||
gulp
|
||||
npm test
|
||||
exit_on_error
|
||||
|
||||
echo ""
|
||||
echo "publish to npm"
|
||||
npm publish
|
||||
exit_on_error
|
||||
|
||||
rm -rf dist
|
||||
echo ""
|
||||
echo "publish to bower"
|
||||
|
||||
git clone git@github.com:ripple/bower-ripple.git dist
|
||||
gulp bower
|
||||
exit_on_error
|
||||
|
||||
cd dist
|
||||
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
|
||||
echo "version: $version"
|
||||
git add ripple.js ripple-debug.js ripple-min.js bower.json
|
||||
exit_on_error
|
||||
|
||||
git commit -m "[TASK] add v$version"
|
||||
exit_on_error
|
||||
|
||||
git tag "v$version"
|
||||
exit_on_error
|
||||
|
||||
git push origin master
|
||||
git push --tags origin master
|
||||
|
||||
cd ..
|
||||
43
scripts/publish_rc
Normal file
43
scripts/publish_rc
Normal file
@@ -0,0 +1,43 @@
|
||||
echo "PUBLISH RELEASE CANDIDATE"
|
||||
|
||||
function exit_on_error {
|
||||
res=$?
|
||||
[[ ${res:-99} -eq 0 ]] || exit $res
|
||||
}
|
||||
|
||||
rm -rf build
|
||||
|
||||
npm install
|
||||
gulp
|
||||
npm test
|
||||
exit_on_error
|
||||
|
||||
echo ""
|
||||
echo "publish rc to npm"
|
||||
npm publish --tag beta
|
||||
exit_on_error
|
||||
|
||||
rm -rf dist
|
||||
echo ""
|
||||
echo "publish to bower"
|
||||
|
||||
git clone git@github.com:ripple/bower-ripple.git dist
|
||||
gulp bower
|
||||
exit_on_error
|
||||
|
||||
cd dist
|
||||
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
|
||||
echo "version: $version"
|
||||
git add ripple.js ripple-debug.js ripple-min.js bower.json
|
||||
exit_on_error
|
||||
|
||||
git commit -m "[TASK] add v$version"
|
||||
exit_on_error
|
||||
|
||||
git tag "v$version"
|
||||
exit_on_error
|
||||
|
||||
git push origin master
|
||||
git push --tags origin master
|
||||
|
||||
cd ..
|
||||
12
scripts/publish_to_bower
Normal file
12
scripts/publish_to_bower
Normal file
@@ -0,0 +1,12 @@
|
||||
rm -rf dist
|
||||
git clone git@github.com:ripple/bower-ripple.git dist
|
||||
gulp bower
|
||||
cd dist
|
||||
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
|
||||
echo "version: $version"
|
||||
git add ripple.js ripple-debug.js ripple-min.js bower.json
|
||||
git commit -m "[TASK] add v$version"
|
||||
git tag "v$version"
|
||||
git push origin master
|
||||
git push --tags origin master
|
||||
cd ..
|
||||
49
scripts/verify_ledger_json.js
Executable file
49
scripts/verify_ledger_json.js
Executable file
@@ -0,0 +1,49 @@
|
||||
var fs = require('fs');
|
||||
var Ledger = require('../src/js/ripple/ledger').Ledger;
|
||||
|
||||
function parse_options(from, flags) {
|
||||
var argv = from.slice(),
|
||||
opts = {argv:argv};
|
||||
|
||||
flags.forEach(function(f) {
|
||||
// Do we have the flag?
|
||||
var flag_index = argv.indexOf('--' + f);
|
||||
// normalize the name of the flag
|
||||
f = f.replace('-', '_');
|
||||
// opts has Boolean value for normalized flag key
|
||||
opts[f] = !!~flag_index;
|
||||
if (opts[f]) {
|
||||
// remove the flag from the argv
|
||||
argv.splice(flag_index, 1);
|
||||
}
|
||||
});
|
||||
return opts;
|
||||
}
|
||||
|
||||
var opts = parse_options(process.argv.slice(2), // remove `node` and `this.js`
|
||||
['sanity-test']);
|
||||
|
||||
if (opts.argv.length < 1) {
|
||||
console.error("Usage: scripts/verify_ledger_json path/to/ledger.json");
|
||||
console.error(" optional: --sanity-test (json>binary>json>binary)");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var json = fs.readFileSync(opts.argv[0], 'utf-8');
|
||||
var ledger = Ledger.from_json(JSON.parse(json));
|
||||
|
||||
// This will serialize each accountState object to binary and then back to json
|
||||
// before finally serializing for hashing. This is mostly to expose any issues
|
||||
// with ripple-libs binary <--> json codecs.
|
||||
if (opts.sanity_test) {
|
||||
console.log("All accountState nodes will be processed from " +
|
||||
"json->binary->json->binary. This may take some time " +
|
||||
"with large ledgers.");
|
||||
}
|
||||
|
||||
console.log("Transaction hash in header: " + ledger.ledger_json.transaction_hash);
|
||||
console.log("Calculated transaction hash: " + ledger.calc_tx_hash().to_hex());
|
||||
console.log("Account state hash in header: " + ledger.ledger_json.account_hash);
|
||||
console.log("Calculated account state hash: " + ledger.calc_account_hash(
|
||||
{sanity_test:opts.sanity_test})
|
||||
.to_hex());
|
||||
@@ -1,15 +1,8 @@
|
||||
// Derived from Tom Wu's jsbn code.
|
||||
//
|
||||
// Changes made for clean up and to package as a node.js module.
|
||||
|
||||
// Copyright (c) 2005-2009 Tom Wu
|
||||
// Copyright (c) 2005 Tom Wu
|
||||
// All Rights Reserved.
|
||||
// See "LICENSE" for details.
|
||||
|
||||
// Basic JavaScript BN library - subset useful for RSA encryption.
|
||||
// Extended JavaScript BN functions, required for RSA private ops.
|
||||
// Version 1.1: new BigInteger("0", 10) returns "proper" zero
|
||||
// Version 1.2: square() API, isProbablePrime fix
|
||||
|
||||
// Bits per digit
|
||||
var dbits;
|
||||
@@ -19,15 +12,15 @@ var canary = 0xdeadbeefcafe;
|
||||
var j_lm = ((canary&0xffffff)==0xefcafe);
|
||||
|
||||
// (public) Constructor
|
||||
var BigInteger = function BigInteger(a,b,c) {
|
||||
function BigInteger(a,b,c) {
|
||||
if(a != null)
|
||||
if("number" == typeof a) this.fromNumber(a,b,c);
|
||||
else if(b == null && "string" != typeof a) this.fromString(a,256);
|
||||
else this.fromString(a,b);
|
||||
};
|
||||
}
|
||||
|
||||
// return new, unset BigInteger
|
||||
var nbi = function nbi() { return new BigInteger(null); };
|
||||
function nbi() { return new BigInteger(null); }
|
||||
|
||||
// am: Compute w_j += (x*this_i), propagate carries,
|
||||
// c is initial carry, returns final carry.
|
||||
@@ -74,7 +67,6 @@ function am3(i,x,w,j,c,n) {
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
if(j_lm && 'undefined' !== typeof navigator && (navigator.appName == "Microsoft Internet Explorer")) {
|
||||
BigInteger.prototype.am = am2;
|
||||
dbits = 30;
|
||||
@@ -126,7 +118,7 @@ function bnpFromInt(x) {
|
||||
this.t = 1;
|
||||
this.s = (x<0)?-1:0;
|
||||
if(x > 0) this[0] = x;
|
||||
else if(x < -1) this[0] = x+DV;
|
||||
else if(x < -1) this[0] = x+this.DV;
|
||||
else this.t = 0;
|
||||
}
|
||||
|
||||
@@ -1122,6 +1114,7 @@ function bnpMillerRabin(t) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// protected
|
||||
BigInteger.prototype.chunkSize = bnpChunkSize;
|
||||
BigInteger.prototype.toRadix = bnpToRadix;
|
||||
@@ -1137,7 +1130,31 @@ BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo;
|
||||
BigInteger.prototype.modInt = bnpModInt;
|
||||
BigInteger.prototype.millerRabin = bnpMillerRabin;
|
||||
|
||||
BigInteger.prototype.copyTo = bnpCopyTo;
|
||||
BigInteger.prototype.fromInt = bnpFromInt;
|
||||
BigInteger.prototype.fromString = bnpFromString;
|
||||
BigInteger.prototype.clamp = bnpClamp;
|
||||
BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
|
||||
BigInteger.prototype.drShiftTo = bnpDRShiftTo;
|
||||
BigInteger.prototype.lShiftTo = bnpLShiftTo;
|
||||
BigInteger.prototype.rShiftTo = bnpRShiftTo;
|
||||
BigInteger.prototype.subTo = bnpSubTo;
|
||||
BigInteger.prototype.multiplyTo = bnpMultiplyTo;
|
||||
BigInteger.prototype.squareTo = bnpSquareTo;
|
||||
BigInteger.prototype.divRemTo = bnpDivRemTo;
|
||||
BigInteger.prototype.invDigit = bnpInvDigit;
|
||||
BigInteger.prototype.isEven = bnpIsEven;
|
||||
BigInteger.prototype.exp = bnpExp;
|
||||
|
||||
// public
|
||||
BigInteger.prototype.toString = bnToString;
|
||||
BigInteger.prototype.negate = bnNegate;
|
||||
BigInteger.prototype.abs = bnAbs;
|
||||
BigInteger.prototype.compareTo = bnCompareTo;
|
||||
BigInteger.prototype.bitLength = bnBitLength;
|
||||
BigInteger.prototype.mod = bnMod;
|
||||
BigInteger.prototype.modPowInt = bnModPowInt;
|
||||
|
||||
BigInteger.prototype.clone = bnClone;
|
||||
BigInteger.prototype.intValue = bnIntValue;
|
||||
BigInteger.prototype.byteValue = bnByteValue;
|
||||
@@ -1175,6 +1192,10 @@ BigInteger.prototype.isProbablePrime = bnIsProbablePrime;
|
||||
// JSBN-specific extension
|
||||
BigInteger.prototype.square = bnSquare;
|
||||
|
||||
// "constants"
|
||||
BigInteger.ZERO = nbv(0);
|
||||
BigInteger.ONE = nbv(1);
|
||||
|
||||
// BigInteger interfaces not implemented in jsbn:
|
||||
|
||||
// BigInteger(int signum, byte[] magnitude)
|
||||
@@ -1183,37 +1204,7 @@ BigInteger.prototype.square = bnSquare;
|
||||
// int hashCode()
|
||||
// long longValue()
|
||||
// static BigInteger valueOf(long val)
|
||||
// protected
|
||||
BigInteger.prototype.copyTo = bnpCopyTo;
|
||||
BigInteger.prototype.fromInt = bnpFromInt;
|
||||
BigInteger.prototype.fromString = bnpFromString;
|
||||
BigInteger.prototype.clamp = bnpClamp;
|
||||
BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
|
||||
BigInteger.prototype.drShiftTo = bnpDRShiftTo;
|
||||
BigInteger.prototype.lShiftTo = bnpLShiftTo;
|
||||
BigInteger.prototype.rShiftTo = bnpRShiftTo;
|
||||
BigInteger.prototype.subTo = bnpSubTo;
|
||||
BigInteger.prototype.multiplyTo = bnpMultiplyTo;
|
||||
BigInteger.prototype.squareTo = bnpSquareTo;
|
||||
BigInteger.prototype.divRemTo = bnpDivRemTo;
|
||||
BigInteger.prototype.invDigit = bnpInvDigit;
|
||||
BigInteger.prototype.isEven = bnpIsEven;
|
||||
BigInteger.prototype.exp = bnpExp;
|
||||
|
||||
// public
|
||||
BigInteger.prototype.toString = bnToString;
|
||||
BigInteger.prototype.negate = bnNegate;
|
||||
BigInteger.prototype.abs = bnAbs;
|
||||
BigInteger.prototype.compareTo = bnCompareTo;
|
||||
BigInteger.prototype.bitLength = bnBitLength;
|
||||
BigInteger.prototype.mod = bnMod;
|
||||
BigInteger.prototype.modPowInt = bnModPowInt;
|
||||
BigInteger.valueOf = nbi;
|
||||
|
||||
// "constants"
|
||||
BigInteger.ZERO = nbv(0);
|
||||
BigInteger.ONE = nbv(1);
|
||||
|
||||
exports.nbi = nbi;
|
||||
exports.BigInteger = BigInteger;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
exports.BigInteger = BigInteger;
|
||||
@@ -3,24 +3,32 @@
|
||||
// You should not instantiate this class yourself, instead use Remote#account.
|
||||
//
|
||||
// Events:
|
||||
// wallet_clean : True, iff the wallet has been updated.
|
||||
// wallet_dirty : True, iff the wallet needs to be updated.
|
||||
// balance : The current stamp balance.
|
||||
// wallet_clean : True, iff the wallet has been updated.
|
||||
// wallet_dirty : True, iff the wallet needs to be updated.
|
||||
// balance: The current stamp balance.
|
||||
// balance_proposed
|
||||
//
|
||||
|
||||
// var network = require("./network.js");
|
||||
// var network = require('./network.js');
|
||||
var async = require('async');
|
||||
var util = require('util');
|
||||
var extend = require('extend');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Amount = require('./amount').Amount;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var TransactionManager = require('./transactionmanager').TransactionManager;
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var Base = require('./base').Base;
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
/**
|
||||
* @constructor Account
|
||||
* @param {Remote} remote
|
||||
* @param {String} account
|
||||
*/
|
||||
|
||||
var Amount = require('./amount').Amount;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
|
||||
var extend = require('extend');
|
||||
|
||||
var Account = function (remote, account) {
|
||||
function Account(remote, account) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
this._remote = remote;
|
||||
@@ -30,48 +38,67 @@ var Account = function (remote, account) {
|
||||
|
||||
// Ledger entry object
|
||||
// Important: This must never be overwritten, only extend()-ed
|
||||
this._entry = {};
|
||||
this._entry = { };
|
||||
|
||||
this.on('newListener', function (type, listener) {
|
||||
if (Account.subscribe_events.indexOf(type) !== -1) {
|
||||
if (!self._subs && 'open' === self._remote._online_state) {
|
||||
function listenerAdded(type, listener) {
|
||||
if (~Account.subscribeEvents.indexOf(type)) {
|
||||
if (!self._subs && self._remote._connected) {
|
||||
self._remote.request_subscribe()
|
||||
.accounts(self._account_id)
|
||||
.request();
|
||||
.add_account(self._account_id)
|
||||
.broadcast();
|
||||
}
|
||||
self._subs += 1;
|
||||
self._subs += 1;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.on('removeListener', function (type, listener) {
|
||||
if (Account.subscribe_events.indexOf(type) !== -1) {
|
||||
self._subs -= 1;
|
||||
this.on('newListener', listenerAdded);
|
||||
|
||||
if (!self._subs && 'open' === self._remote._online_state) {
|
||||
function listenerRemoved(type, listener) {
|
||||
if (~Account.subscribeEvents.indexOf(type)) {
|
||||
self._subs -= 1;
|
||||
if (!self._subs && self._remote._connected) {
|
||||
self._remote.request_unsubscribe()
|
||||
.accounts(self._account_id)
|
||||
.request();
|
||||
.add_account(self._account_id)
|
||||
.broadcast();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this._remote.on('prepare_subscribe', function (request) {
|
||||
if (self._subs) request.accounts(self._account_id);
|
||||
});
|
||||
this.on('removeListener', listenerRemoved);
|
||||
|
||||
function attachAccount(request) {
|
||||
if (self._account.is_valid() && self._subs) {
|
||||
request.add_account(self._account_id);
|
||||
}
|
||||
};
|
||||
|
||||
this._remote.on('prepare_subscribe', attachAccount);
|
||||
|
||||
function handleTransaction(transaction) {
|
||||
if (!transaction.mmeta) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.on('transaction', function (msg) {
|
||||
var changed = false;
|
||||
msg.mmeta.each(function (an) {
|
||||
if (an.entryType === 'AccountRoot' &&
|
||||
an.fields.Account === self._account_id) {
|
||||
|
||||
transaction.mmeta.each(function(an) {
|
||||
var isAccount = an.fields.Account === self._account_id;
|
||||
var isAccountRoot = isAccount && (an.entryType === 'AccountRoot');
|
||||
|
||||
if (isAccountRoot) {
|
||||
extend(self._entry, an.fieldsNew, an.fieldsFinal);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (changed) {
|
||||
self.emit('entry', self._entry);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.on('transaction', handleTransaction);
|
||||
|
||||
this._transactionManager = new TransactionManager(this);
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -81,10 +108,10 @@ util.inherits(Account, EventEmitter);
|
||||
/**
|
||||
* List of events that require a remote subscription to the account.
|
||||
*/
|
||||
Account.subscribe_events = ['transaction', 'entry'];
|
||||
|
||||
Account.prototype.to_json = function ()
|
||||
{
|
||||
Account.subscribeEvents = [ 'transaction', 'entry' ];
|
||||
|
||||
Account.prototype.toJson = function() {
|
||||
return this._account.to_json();
|
||||
};
|
||||
|
||||
@@ -93,36 +120,70 @@ Account.prototype.to_json = function ()
|
||||
*
|
||||
* Note: This does not tell you whether the account exists in the ledger.
|
||||
*/
|
||||
Account.prototype.is_valid = function ()
|
||||
{
|
||||
|
||||
Account.prototype.isValid = function() {
|
||||
return this._account.is_valid();
|
||||
};
|
||||
|
||||
/**
|
||||
* Request account info
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
Account.prototype.getInfo = function(callback) {
|
||||
return this._remote.requestAccountInfo({account: this._account_id}, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the current AccountRoot entry.
|
||||
*
|
||||
* To keep up-to-date with changes to the AccountRoot entry, subscribe to the
|
||||
* "entry" event.
|
||||
* 'entry' event.
|
||||
*
|
||||
* @param {function (err, entry)} callback Called with the result
|
||||
* @param {Function} callback
|
||||
*/
|
||||
Account.prototype.entry = function (callback)
|
||||
{
|
||||
|
||||
Account.prototype.entry = function(callback) {
|
||||
var self = this;
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
|
||||
self._remote.request_account_info(this._account_id)
|
||||
.on('success', function (e) {
|
||||
extend(self._entry, e.account_data);
|
||||
function accountInfo(err, info) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
extend(self._entry, info.account_data);
|
||||
self.emit('entry', self._entry);
|
||||
callback(null, info);
|
||||
}
|
||||
};
|
||||
|
||||
if ("function" === typeof callback) {
|
||||
callback(null, e);
|
||||
}
|
||||
})
|
||||
.on('error', function (e) {
|
||||
callback(e);
|
||||
})
|
||||
.request();
|
||||
this.getInfo(accountInfo);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Account.prototype.getNextSequence = function(callback) {
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
|
||||
function isNotFound(err) {
|
||||
return err && typeof err === 'object'
|
||||
&& typeof err.remote === 'object'
|
||||
&& err.remote.error === 'actNotFound';
|
||||
};
|
||||
|
||||
function accountInfo(err, info) {
|
||||
if (isNotFound(err)) {
|
||||
// New accounts will start out as sequence one
|
||||
callback(null, 1);
|
||||
} else if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, info.account_data.Sequence);
|
||||
}
|
||||
};
|
||||
|
||||
this.getInfo(accountInfo);
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -131,27 +192,61 @@ Account.prototype.entry = function (callback)
|
||||
* Retrieve this account's Ripple trust lines.
|
||||
*
|
||||
* To keep up-to-date with changes to the AccountRoot entry, subscribe to the
|
||||
* "lines" event. (Not yet implemented.)
|
||||
* 'lines' event. (Not yet implemented.)
|
||||
*
|
||||
* @param {function (err, lines)} callback Called with the result
|
||||
* @param {function(err, lines)} callback Called with the result
|
||||
*/
|
||||
Account.prototype.lines = function (callback)
|
||||
{
|
||||
|
||||
Account.prototype.lines = function(callback) {
|
||||
var self = this;
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
|
||||
self._remote.request_account_lines(this._account_id)
|
||||
.on('success', function (e) {
|
||||
self._lines = e.lines;
|
||||
function accountLines(err, res) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
self._lines = res.lines;
|
||||
self.emit('lines', self._lines);
|
||||
callback(null, res);
|
||||
}
|
||||
}
|
||||
|
||||
if ("function" === typeof callback) {
|
||||
callback(null, e);
|
||||
this._remote.requestAccountLines({account: this._account_id}, accountLines);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve this account's single trust line.
|
||||
*
|
||||
* @param {string} currency Currency
|
||||
* @param {string} address Ripple address
|
||||
* @param {function(err, line)} callback Called with the result
|
||||
* @returns {Account}
|
||||
*/
|
||||
|
||||
Account.prototype.line = function(currency, address, callback) {
|
||||
var self = this;
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
|
||||
self.lines(function(err, data) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var line;
|
||||
|
||||
top:
|
||||
for (var i=0; i<data.lines.length; i++) {
|
||||
var l = data.lines[i];
|
||||
if (l.account === address && l.currency === currency) {
|
||||
line = l;
|
||||
break top;
|
||||
}
|
||||
})
|
||||
.on('error', function (e) {
|
||||
callback(e);
|
||||
})
|
||||
.request();
|
||||
}
|
||||
|
||||
callback(null, line);
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -161,17 +256,141 @@ Account.prototype.lines = function (callback)
|
||||
*
|
||||
* This is only meant to be called by the Remote class. You should never have to
|
||||
* call this yourself.
|
||||
*
|
||||
* @param {Object} message
|
||||
*/
|
||||
Account.prototype.notifyTx = function (message)
|
||||
{
|
||||
|
||||
Account.prototype.notify =
|
||||
Account.prototype.notifyTx = function(transaction) {
|
||||
// Only trigger the event if the account object is actually
|
||||
// subscribed - this prevents some weird phantom events from
|
||||
// occurring.
|
||||
if (this._subs) {
|
||||
this.emit('transaction', message);
|
||||
if (!this._subs) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('transaction', transaction);
|
||||
|
||||
var account = transaction.transaction.Account;
|
||||
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
|
||||
var isThisAccount = (account === this._account_id);
|
||||
|
||||
this.emit(isThisAccount ? 'transaction-outbound' : 'transaction-inbound', transaction);
|
||||
};
|
||||
|
||||
/**
|
||||
* Submit a transaction to an account's
|
||||
* transaction manager
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
|
||||
Account.prototype.submit = function(transaction) {
|
||||
this._transactionManager.submit(transaction);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the given public key is valid for this account
|
||||
*
|
||||
* @param {Hex-encoded String|RippleAddress} public_key
|
||||
* @param {Function} callback
|
||||
*
|
||||
* @callback
|
||||
* @param {Error} err
|
||||
* @param {Boolean} true if the public key is valid and active, false otherwise
|
||||
*/
|
||||
Account.prototype.publicKeyIsActive = function(public_key, callback) {
|
||||
var self = this;
|
||||
var public_key_as_uint160;
|
||||
|
||||
try {
|
||||
public_key_as_uint160 = Account._publicKeyToAddress(public_key);
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
function getAccountInfo(async_callback) {
|
||||
self.getInfo(function(err, account_info_res){
|
||||
|
||||
// If the remote responds with an Account Not Found error then the account
|
||||
// is unfunded and thus we can assume that the master key is active
|
||||
if (err && err.remote && err.remote.error === 'actNotFound') {
|
||||
async_callback(null, null);
|
||||
} else {
|
||||
async_callback(err, account_info_res);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function publicKeyIsValid(account_info_res, async_callback) {
|
||||
// Catch the case of unfunded accounts
|
||||
if (!account_info_res) {
|
||||
|
||||
if (public_key_as_uint160 === self._account_id) {
|
||||
async_callback(null, true);
|
||||
} else {
|
||||
async_callback(null, false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var account_info = account_info_res.account_data;
|
||||
|
||||
// Respond with true if the RegularKey is set and matches the given public key or
|
||||
// if the public key matches the account address and the lsfDisableMaster is not set
|
||||
if (account_info.RegularKey &&
|
||||
account_info.RegularKey === public_key_as_uint160) {
|
||||
async_callback(null, true);
|
||||
} else if (account_info.Account === public_key_as_uint160 &&
|
||||
((account_info.Flags & 0x00100000) === 0)) {
|
||||
async_callback(null, true);
|
||||
} else {
|
||||
async_callback(null, false);
|
||||
}
|
||||
};
|
||||
|
||||
var steps = [
|
||||
getAccountInfo,
|
||||
publicKeyIsValid
|
||||
];
|
||||
|
||||
async.waterfall(steps, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a hex-encoded public key to a Ripple Address
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {Hex-encoded string|RippleAddress} public_key
|
||||
* @returns {RippleAddress}
|
||||
*/
|
||||
Account._publicKeyToAddress = function(public_key) {
|
||||
// Based on functions in /src/js/ripple/keypair.js
|
||||
function hexToUInt160(public_key) {
|
||||
var bits = sjcl.codec.hex.toBits(public_key);
|
||||
var hash = sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
||||
var address = UInt160.from_bits(hash);
|
||||
address.set_version(Base.VER_ACCOUNT_ID);
|
||||
|
||||
return address.to_json();
|
||||
};
|
||||
|
||||
if (UInt160.is_valid(public_key)) {
|
||||
return public_key;
|
||||
} else if (/^[0-9a-fA-F]+$/.test(public_key)) {
|
||||
return hexToUInt160(public_key);
|
||||
} else {
|
||||
throw new Error('Public key is invalid. Must be a UInt160 or a hex string');
|
||||
}
|
||||
};
|
||||
|
||||
exports.Account = Account;
|
||||
exports.Account = Account;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
59
src/js/ripple/authinfo.js
Normal file
59
src/js/ripple/authinfo.js
Normal file
@@ -0,0 +1,59 @@
|
||||
var async = require('async');
|
||||
var superagent = require('superagent');
|
||||
var RippleTxt = require('./rippletxt').RippleTxt;
|
||||
|
||||
var AuthInfo = { };
|
||||
|
||||
AuthInfo._getRippleTxt = function(domain, callback) {
|
||||
RippleTxt.get(domain, callback);
|
||||
};
|
||||
|
||||
AuthInfo._getUser = function(url, callback) {
|
||||
superagent.get(url, callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get auth info for a given username
|
||||
*
|
||||
* @param {string} domain - Domain which hosts the user's info
|
||||
* @param {string} username - Username who's info we are retreiving
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
|
||||
AuthInfo.get = function(domain, username, callback) {
|
||||
var self = this;
|
||||
username = username.toLowerCase();
|
||||
|
||||
function getRippleTxt(callback) {
|
||||
self._getRippleTxt(domain, function(err, txt) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!txt.authinfo_url) {
|
||||
return callback(new Error('Authentication is not supported on ' + domain));
|
||||
}
|
||||
|
||||
var url = Array.isArray(txt.authinfo_url) ? txt.authinfo_url[0] : txt.authinfo_url;
|
||||
|
||||
url += '?domain=' + domain + '&username=' + username;
|
||||
|
||||
callback(null, url);
|
||||
});
|
||||
};
|
||||
|
||||
function getUser(url, callback) {
|
||||
self._getUser(url, function(err, res) {
|
||||
if (err || res.error) {
|
||||
callback(new Error('Authentication info server unreachable'));
|
||||
} else {
|
||||
callback(null, res.body);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async.waterfall([ getRippleTxt, getUser ], callback);
|
||||
};
|
||||
|
||||
exports.AuthInfo = AuthInfo;
|
||||
@@ -1,161 +1,169 @@
|
||||
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var utils = require('./utils');
|
||||
var jsbn = require('./jsbn');
|
||||
var extend = require('extend');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var nbi = jsbn.nbi;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
var Base = {};
|
||||
|
||||
var alphabets = Base.alphabets = {
|
||||
'ripple' : "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz",
|
||||
'tipple' : "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz",
|
||||
'bitcoin' : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
var alphabets = Base.alphabets = {
|
||||
ripple: 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz',
|
||||
tipple: 'RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz',
|
||||
bitcoin: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||
};
|
||||
|
||||
extend(Base, {
|
||||
'VER_NONE' : 1,
|
||||
'VER_NODE_PUBLIC' : 28,
|
||||
'VER_NODE_PRIVATE' : 32,
|
||||
'VER_ACCOUNT_ID' : 0,
|
||||
'VER_ACCOUNT_PUBLIC' : 35,
|
||||
'VER_ACCOUNT_PRIVATE' : 34,
|
||||
'VER_FAMILY_GENERATOR' : 41,
|
||||
'VER_FAMILY_SEED' : 33
|
||||
VER_NONE : 1,
|
||||
VER_NODE_PUBLIC : 28,
|
||||
VER_NODE_PRIVATE : 32,
|
||||
VER_ACCOUNT_ID : 0,
|
||||
VER_ACCOUNT_PUBLIC : 35,
|
||||
VER_ACCOUNT_PRIVATE : 34,
|
||||
VER_FAMILY_GENERATOR : 41,
|
||||
VER_FAMILY_SEED : 33
|
||||
});
|
||||
|
||||
var sha256 = function (bytes) {
|
||||
function sha256(bytes) {
|
||||
return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes)));
|
||||
};
|
||||
|
||||
var sha256hash = function (bytes) {
|
||||
function sha256hash(bytes) {
|
||||
return sha256(sha256(bytes));
|
||||
};
|
||||
|
||||
// --> input: big-endian array of bytes.
|
||||
// <-- string at least as long as input.
|
||||
Base.encode = function (input, alpha) {
|
||||
var alphabet = alphabets[alpha || 'ripple'];
|
||||
var bi_base = new BigInteger(String(alphabet.length));
|
||||
var bi_q = nbi();
|
||||
var bi_r = nbi();
|
||||
var bi_value = new BigInteger(input);
|
||||
var buffer = [];
|
||||
Base.encode = function(input, alpha) {
|
||||
var alphabet = alphabets[alpha || 'ripple'];
|
||||
var bi_base = new BigInteger(String(alphabet.length));
|
||||
var bi_q = new BigInteger();
|
||||
var bi_r = new BigInteger();
|
||||
var bi_value = new BigInteger(input);
|
||||
var buffer = [];
|
||||
|
||||
while (bi_value.compareTo(BigInteger.ZERO) > 0)
|
||||
{
|
||||
while (bi_value.compareTo(BigInteger.ZERO) > 0) {
|
||||
bi_value.divRemTo(bi_base, bi_q, bi_r);
|
||||
bi_q.copyTo(bi_value);
|
||||
|
||||
buffer.push(alphabet[bi_r.intValue()]);
|
||||
}
|
||||
|
||||
var i;
|
||||
|
||||
for (i = 0; i != input.length && !input[i]; i += 1) {
|
||||
for (var i=0; i !== input.length && !input[i]; i += 1) {
|
||||
buffer.push(alphabet[0]);
|
||||
}
|
||||
|
||||
return buffer.reverse().join("");
|
||||
return buffer.reverse().join('');
|
||||
};
|
||||
|
||||
// --> input: String
|
||||
// <-- array of bytes or undefined.
|
||||
Base.decode = function (input, alpha) {
|
||||
if ("string" !== typeof input) return undefined;
|
||||
Base.decode = function(input, alpha) {
|
||||
if (typeof input !== 'string') {
|
||||
return void(0);
|
||||
}
|
||||
|
||||
var alphabet = alphabets[alpha || 'ripple'];
|
||||
var bi_base = new BigInteger(String(alphabet.length));
|
||||
var bi_value = nbi();
|
||||
var alphabet = alphabets[alpha || 'ripple'];
|
||||
var bi_base = new BigInteger(String(alphabet.length));
|
||||
var bi_value = new BigInteger();
|
||||
var i;
|
||||
|
||||
for (i = 0; i != input.length && input[i] === alphabet[0]; i += 1)
|
||||
;
|
||||
for (i = 0; i !== input.length && input[i] === alphabet[0]; i += 1) {
|
||||
}
|
||||
|
||||
for (; i != input.length; i += 1) {
|
||||
var v = alphabet.indexOf(input[i]);
|
||||
for (; i !== input.length; i += 1) {
|
||||
var v = alphabet.indexOf(input[i]);
|
||||
|
||||
if (v < 0)
|
||||
return undefined;
|
||||
|
||||
var r = nbi();
|
||||
if (v < 0) {
|
||||
return void(0);
|
||||
}
|
||||
|
||||
var r = new BigInteger();
|
||||
r.fromInt(v);
|
||||
|
||||
bi_value = bi_value.multiply(bi_base).add(r);
|
||||
}
|
||||
|
||||
// toByteArray:
|
||||
// - Returns leading zeros!
|
||||
// - Returns signed bytes!
|
||||
var bytes = bi_value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0; });
|
||||
var bytes = bi_value.toByteArray().map(function(b) { return b ? b < 0 ? 256+b : b : 0; });
|
||||
var extra = 0;
|
||||
|
||||
while (extra != bytes.length && !bytes[extra])
|
||||
while (extra !== bytes.length && !bytes[extra]) {
|
||||
extra += 1;
|
||||
}
|
||||
|
||||
if (extra)
|
||||
if (extra) {
|
||||
bytes = bytes.slice(extra);
|
||||
}
|
||||
|
||||
var zeros = 0;
|
||||
|
||||
while (zeros !== input.length && input[zeros] === alphabet[0])
|
||||
while (zeros !== input.length && input[zeros] === alphabet[0]) {
|
||||
zeros += 1;
|
||||
}
|
||||
|
||||
return [].concat(utils.arraySet(zeros, 0), bytes);
|
||||
};
|
||||
|
||||
Base.verify_checksum = function (bytes) {
|
||||
var computed = sha256hash(bytes.slice(0, -4)).slice(0, 4);
|
||||
var checksum = bytes.slice(-4);
|
||||
Base.verify_checksum = function(bytes) {
|
||||
var computed = sha256hash(bytes.slice(0, -4)).slice(0, 4);
|
||||
var checksum = bytes.slice(-4);
|
||||
var result = true;
|
||||
|
||||
for (var i = 0; i < 4; i++)
|
||||
if (computed[i] !== checksum[i])
|
||||
return false;
|
||||
for (var i=0; i<4; i++) {
|
||||
if (computed[i] !== checksum[i]) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return result;
|
||||
};
|
||||
|
||||
// --> input: Array
|
||||
// <-- String
|
||||
Base.encode_check = function (version, input, alphabet) {
|
||||
var buffer = [].concat(version, input);
|
||||
var check = sha256(sha256(buffer)).slice(0, 4);
|
||||
Base.encode_check = function(version, input, alphabet) {
|
||||
var buffer = [].concat(version, input);
|
||||
var check = sha256(sha256(buffer)).slice(0, 4);
|
||||
|
||||
return Base.encode([].concat(buffer, check), alphabet);
|
||||
}
|
||||
};
|
||||
|
||||
// --> input : String
|
||||
// <-- NaN || BigInteger
|
||||
Base.decode_check = function (version, input, alphabet) {
|
||||
Base.decode_check = function(version, input, alphabet) {
|
||||
var buffer = Base.decode(input, alphabet);
|
||||
|
||||
if (!buffer || buffer.length < 5)
|
||||
if (!buffer || buffer.length < 5) {
|
||||
return NaN;
|
||||
|
||||
// Single valid version
|
||||
if ("number" === typeof version && buffer[0] !== version)
|
||||
return NaN;
|
||||
|
||||
// Multiple allowed versions
|
||||
if ("object" === typeof version && Array.isArray(version)) {
|
||||
var match = false;
|
||||
for (var i = 0, l = version.length; i < l; i++) {
|
||||
match |= version[i] === buffer[0];
|
||||
}
|
||||
if (!match) return NaN;
|
||||
}
|
||||
|
||||
if (!Base.verify_checksum(buffer))
|
||||
// Single valid version
|
||||
if (typeof version === 'number' && buffer[0] !== version) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
// Multiple allowed versions
|
||||
if (Array.isArray(version)) {
|
||||
var match = false;
|
||||
|
||||
for (var i=0, l=version.length; i<l; i++) {
|
||||
match |= version[i] === buffer[0];
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
return NaN;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Base.verify_checksum(buffer)) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
// We'll use the version byte to add a leading zero, this ensures JSBN doesn't
|
||||
// intrepret the value as a negative number
|
||||
buffer[0] = 0;
|
||||
|
||||
return new BigInteger(buffer.slice(0, -4), 256);
|
||||
}
|
||||
};
|
||||
|
||||
exports.Base = Base;
|
||||
|
||||
@@ -1,90 +1,413 @@
|
||||
var ST = require("./serializedtypes");
|
||||
/**
|
||||
* Data type map.
|
||||
*
|
||||
* Mapping of type ids to data types. The type id is specified by the high
|
||||
*/
|
||||
var TYPES_MAP = exports.types = [
|
||||
void(0),
|
||||
|
||||
// Common
|
||||
'Int16', // 1
|
||||
'Int32', // 2
|
||||
'Int64', // 3
|
||||
'Hash128', // 4
|
||||
'Hash256', // 5
|
||||
'Amount', // 6
|
||||
'VL', // 7
|
||||
'Account', // 8
|
||||
|
||||
// 9-13 reserved
|
||||
void(0), // 9
|
||||
void(0), // 10
|
||||
void(0), // 11
|
||||
void(0), // 12
|
||||
void(0), // 13
|
||||
|
||||
'Object', // 14
|
||||
'Array', // 15
|
||||
|
||||
// Uncommon
|
||||
'Int8', // 16
|
||||
'Hash160', // 17
|
||||
'PathSet', // 18
|
||||
'Vector256' // 19
|
||||
];
|
||||
|
||||
/**
|
||||
* Field type map.
|
||||
*
|
||||
* Mapping of field type id to field type name.
|
||||
*/
|
||||
|
||||
var FIELDS_MAP = exports.fields = {
|
||||
// Common types
|
||||
1: { // Int16
|
||||
1: 'LedgerEntryType',
|
||||
2: 'TransactionType'
|
||||
},
|
||||
2: { // Int32
|
||||
2: 'Flags',
|
||||
3: 'SourceTag',
|
||||
4: 'Sequence',
|
||||
5: 'PreviousTxnLgrSeq',
|
||||
6: 'LedgerSequence',
|
||||
7: 'CloseTime',
|
||||
8: 'ParentCloseTime',
|
||||
9: 'SigningTime',
|
||||
10: 'Expiration',
|
||||
11: 'TransferRate',
|
||||
12: 'WalletSize',
|
||||
13: 'OwnerCount',
|
||||
14: 'DestinationTag',
|
||||
// Skip 15
|
||||
16: 'HighQualityIn',
|
||||
17: 'HighQualityOut',
|
||||
18: 'LowQualityIn',
|
||||
19: 'LowQualityOut',
|
||||
20: 'QualityIn',
|
||||
21: 'QualityOut',
|
||||
22: 'StampEscrow',
|
||||
23: 'BondAmount',
|
||||
24: 'LoadFee',
|
||||
25: 'OfferSequence',
|
||||
26: 'FirstLedgerSequence',
|
||||
27: 'LastLedgerSequence',
|
||||
28: 'TransactionIndex',
|
||||
29: 'OperationLimit',
|
||||
30: 'ReferenceFeeUnits',
|
||||
31: 'ReserveBase',
|
||||
32: 'ReserveIncrement',
|
||||
33: 'SetFlag',
|
||||
34: 'ClearFlag'
|
||||
},
|
||||
3: { // Int64
|
||||
1: 'IndexNext',
|
||||
2: 'IndexPrevious',
|
||||
3: 'BookNode',
|
||||
4: 'OwnerNode',
|
||||
5: 'BaseFee',
|
||||
6: 'ExchangeRate',
|
||||
7: 'LowNode',
|
||||
8: 'HighNode'
|
||||
},
|
||||
4: { // Hash128
|
||||
1: 'EmailHash'
|
||||
},
|
||||
5: { // Hash256
|
||||
1: 'LedgerHash',
|
||||
2: 'ParentHash',
|
||||
3: 'TransactionHash',
|
||||
4: 'AccountHash',
|
||||
5: 'PreviousTxnID',
|
||||
6: 'LedgerIndex',
|
||||
7: 'WalletLocator',
|
||||
8: 'RootIndex',
|
||||
9: 'AccountTxnID',
|
||||
16: 'BookDirectory',
|
||||
17: 'InvoiceID',
|
||||
18: 'Nickname',
|
||||
19: 'Feature'
|
||||
},
|
||||
6: { // Amount
|
||||
1: 'Amount',
|
||||
2: 'Balance',
|
||||
3: 'LimitAmount',
|
||||
4: 'TakerPays',
|
||||
5: 'TakerGets',
|
||||
6: 'LowLimit',
|
||||
7: 'HighLimit',
|
||||
8: 'Fee',
|
||||
9: 'SendMax',
|
||||
16: 'MinimumOffer',
|
||||
17: 'RippleEscrow',
|
||||
18: 'DeliveredAmount'
|
||||
},
|
||||
7: { // VL
|
||||
1: 'PublicKey',
|
||||
2: 'MessageKey',
|
||||
3: 'SigningPubKey',
|
||||
4: 'TxnSignature',
|
||||
5: 'Generator',
|
||||
6: 'Signature',
|
||||
7: 'Domain',
|
||||
8: 'FundCode',
|
||||
9: 'RemoveCode',
|
||||
10: 'ExpireCode',
|
||||
11: 'CreateCode',
|
||||
12: 'MemoType',
|
||||
13: 'MemoData'
|
||||
},
|
||||
8: { // Account
|
||||
1: 'Account',
|
||||
2: 'Owner',
|
||||
3: 'Destination',
|
||||
4: 'Issuer',
|
||||
7: 'Target',
|
||||
8: 'RegularKey'
|
||||
},
|
||||
14: { // Object
|
||||
1: void(0), //end of Object
|
||||
2: 'TransactionMetaData',
|
||||
3: 'CreatedNode',
|
||||
4: 'DeletedNode',
|
||||
5: 'ModifiedNode',
|
||||
6: 'PreviousFields',
|
||||
7: 'FinalFields',
|
||||
8: 'NewFields',
|
||||
9: 'TemplateEntry',
|
||||
10: 'Memo'
|
||||
},
|
||||
15: { // Array
|
||||
1: void(0), //end of Array
|
||||
2: 'SigningAccounts',
|
||||
3: 'TxnSignatures',
|
||||
4: 'Signatures',
|
||||
5: 'Template',
|
||||
6: 'Necessary',
|
||||
7: 'Sufficient',
|
||||
8: 'AffectedNodes',
|
||||
9: 'Memos'
|
||||
},
|
||||
|
||||
// Uncommon types
|
||||
16: { // Int8
|
||||
1: 'CloseResolution',
|
||||
2: 'TemplateEntryType',
|
||||
3: 'TransactionResult'
|
||||
},
|
||||
17: { // Hash160
|
||||
1: 'TakerPaysCurrency',
|
||||
2: 'TakerPaysIssuer',
|
||||
3: 'TakerGetsCurrency',
|
||||
4: 'TakerGetsIssuer'
|
||||
},
|
||||
18: { // PathSet
|
||||
1: 'Paths'
|
||||
},
|
||||
19: { // Vector256
|
||||
1: 'Indexes',
|
||||
2: 'Hashes',
|
||||
3: 'Features'
|
||||
}
|
||||
};
|
||||
|
||||
var INVERSE_FIELDS_MAP = exports.fieldsInverseMap = { };
|
||||
|
||||
Object.keys(FIELDS_MAP).forEach(function(k1) {
|
||||
Object.keys(FIELDS_MAP[k1]).forEach(function(k2) {
|
||||
INVERSE_FIELDS_MAP[FIELDS_MAP[k1][k2]] = [ Number(k1), Number(k2) ];
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var REQUIRED = exports.REQUIRED = 0,
|
||||
OPTIONAL = exports.OPTIONAL = 1,
|
||||
DEFAULT = exports.DEFAULT = 2;
|
||||
|
||||
ST.Int16.id = 1;
|
||||
ST.Int32.id = 2;
|
||||
ST.Int64.id = 3;
|
||||
ST.Hash128.id = 4;
|
||||
ST.Hash256.id = 5;
|
||||
ST.Amount.id = 6;
|
||||
ST.VariableLength.id = 7;
|
||||
ST.Account.id = 8;
|
||||
ST.Object.id = 14;
|
||||
ST.Array.id = 15;
|
||||
ST.Int8.id = 16;
|
||||
ST.Hash160.id = 17;
|
||||
ST.PathSet.id = 18;
|
||||
ST.Vector256.id = 19;
|
||||
|
||||
var base = [
|
||||
[ 'TransactionType' , REQUIRED, 2, ST.Int16 ],
|
||||
[ 'Flags' , OPTIONAL, 2, ST.Int32 ],
|
||||
[ 'SourceTag' , OPTIONAL, 3, ST.Int32 ],
|
||||
[ 'Account' , REQUIRED, 1, ST.Account ],
|
||||
[ 'Sequence' , REQUIRED, 4, ST.Int32 ],
|
||||
[ 'Fee' , REQUIRED, 8, ST.Amount ],
|
||||
[ 'OperationLimit' , OPTIONAL, 29, ST.Int32 ],
|
||||
[ 'SigningPubKey' , REQUIRED, 3, ST.VariableLength ],
|
||||
[ 'TxnSignature' , OPTIONAL, 4, ST.VariableLength ]
|
||||
[ 'TransactionType' , REQUIRED ],
|
||||
[ 'Flags' , OPTIONAL ],
|
||||
[ 'SourceTag' , OPTIONAL ],
|
||||
[ 'LastLedgerSequence' , OPTIONAL ],
|
||||
[ 'Account' , REQUIRED ],
|
||||
[ 'Sequence' , REQUIRED ],
|
||||
[ 'Fee' , REQUIRED ],
|
||||
[ 'OperationLimit' , OPTIONAL ],
|
||||
[ 'SigningPubKey' , REQUIRED ],
|
||||
[ 'TxnSignature' , OPTIONAL ]
|
||||
];
|
||||
|
||||
exports.tx = {
|
||||
AccountSet: [3].concat(base, [
|
||||
[ 'EmailHash' , OPTIONAL, 1, ST.Hash128 ],
|
||||
[ 'WalletLocator' , OPTIONAL, 7, ST.Hash256 ],
|
||||
[ 'WalletSize' , OPTIONAL, 12, ST.Int32 ],
|
||||
[ 'MessageKey' , OPTIONAL, 2, ST.VariableLength ],
|
||||
[ 'Domain' , OPTIONAL, 7, ST.VariableLength ],
|
||||
[ 'TransferRate' , OPTIONAL, 11, ST.Int32 ]
|
||||
[ 'EmailHash' , OPTIONAL ],
|
||||
[ 'WalletLocator' , OPTIONAL ],
|
||||
[ 'WalletSize' , OPTIONAL ],
|
||||
[ 'MessageKey' , OPTIONAL ],
|
||||
[ 'Domain' , OPTIONAL ],
|
||||
[ 'TransferRate' , OPTIONAL ]
|
||||
]),
|
||||
TrustSet: [20].concat(base, [
|
||||
[ 'LimitAmount' , OPTIONAL, 3, ST.Amount ],
|
||||
[ 'QualityIn' , OPTIONAL, 20, ST.Int32 ],
|
||||
[ 'QualityOut' , OPTIONAL, 21, ST.Int32 ]
|
||||
[ 'LimitAmount' , OPTIONAL ],
|
||||
[ 'QualityIn' , OPTIONAL ],
|
||||
[ 'QualityOut' , OPTIONAL ]
|
||||
]),
|
||||
OfferCreate: [7].concat(base, [
|
||||
[ 'TakerPays' , REQUIRED, 4, ST.Amount ],
|
||||
[ 'TakerGets' , REQUIRED, 5, ST.Amount ],
|
||||
[ 'Expiration' , OPTIONAL, 10, ST.Int32 ]
|
||||
[ 'TakerPays' , REQUIRED ],
|
||||
[ 'TakerGets' , REQUIRED ],
|
||||
[ 'Expiration' , OPTIONAL ]
|
||||
]),
|
||||
OfferCancel: [8].concat(base, [
|
||||
[ 'OfferSequence' , REQUIRED, 25, ST.Int32 ]
|
||||
[ 'OfferSequence' , REQUIRED ]
|
||||
]),
|
||||
SetRegularKey: [5].concat(base, [
|
||||
[ 'RegularKey' , REQUIRED, 8, ST.Account ]
|
||||
[ 'RegularKey' , REQUIRED ]
|
||||
]),
|
||||
Payment: [0].concat(base, [
|
||||
[ 'Destination' , REQUIRED, 3, ST.Account ],
|
||||
[ 'Amount' , REQUIRED, 1, ST.Amount ],
|
||||
[ 'SendMax' , OPTIONAL, 9, ST.Amount ],
|
||||
[ 'Paths' , DEFAULT , 1, ST.PathSet ],
|
||||
[ 'InvoiceID' , OPTIONAL, 17, ST.Hash256 ],
|
||||
[ 'DestinationTag' , OPTIONAL, 14, ST.Int32 ]
|
||||
[ 'Destination' , REQUIRED ],
|
||||
[ 'Amount' , REQUIRED ],
|
||||
[ 'SendMax' , OPTIONAL ],
|
||||
[ 'Paths' , DEFAULT ],
|
||||
[ 'InvoiceID' , OPTIONAL ],
|
||||
[ 'DestinationTag' , OPTIONAL ]
|
||||
]),
|
||||
Contract: [9].concat(base, [
|
||||
[ 'Expiration' , REQUIRED, 10, ST.Int32 ],
|
||||
[ 'BondAmount' , REQUIRED, 23, ST.Int32 ],
|
||||
[ 'StampEscrow' , REQUIRED, 22, ST.Int32 ],
|
||||
[ 'RippleEscrow' , REQUIRED, 17, ST.Amount ],
|
||||
[ 'CreateCode' , OPTIONAL, 11, ST.VariableLength ],
|
||||
[ 'FundCode' , OPTIONAL, 8, ST.VariableLength ],
|
||||
[ 'RemoveCode' , OPTIONAL, 9, ST.VariableLength ],
|
||||
[ 'ExpireCode' , OPTIONAL, 10, ST.VariableLength ]
|
||||
[ 'Expiration' , REQUIRED ],
|
||||
[ 'BondAmount' , REQUIRED ],
|
||||
[ 'StampEscrow' , REQUIRED ],
|
||||
[ 'RippleEscrow' , REQUIRED ],
|
||||
[ 'CreateCode' , OPTIONAL ],
|
||||
[ 'FundCode' , OPTIONAL ],
|
||||
[ 'RemoveCode' , OPTIONAL ],
|
||||
[ 'ExpireCode' , OPTIONAL ]
|
||||
]),
|
||||
RemoveContract: [10].concat(base, [
|
||||
[ 'Target' , REQUIRED, 7, ST.Account ]
|
||||
[ 'Target' , REQUIRED ]
|
||||
]),
|
||||
EnableFeature: [100].concat(base, [
|
||||
[ 'Feature' , REQUIRED, 19, ST.Hash256 ]
|
||||
[ 'Feature' , REQUIRED ]
|
||||
]),
|
||||
SetFee: [101].concat(base, [
|
||||
[ 'Features' , REQUIRED, 9, ST.Array ],
|
||||
[ 'BaseFee' , REQUIRED, 5, ST.Int64 ],
|
||||
[ 'ReferenceFeeUnits' , REQUIRED, 30, ST.Int32 ],
|
||||
[ 'ReserveBase' , REQUIRED, 31, ST.Int32 ],
|
||||
[ 'ReserveIncrement' , REQUIRED, 32, ST.Int32 ]
|
||||
[ 'Features' , REQUIRED ],
|
||||
[ 'BaseFee' , REQUIRED ],
|
||||
[ 'ReferenceFeeUnits' , REQUIRED ],
|
||||
[ 'ReserveBase' , REQUIRED ],
|
||||
[ 'ReserveIncrement' , REQUIRED ]
|
||||
])
|
||||
};
|
||||
|
||||
var sleBase = [
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED]
|
||||
];
|
||||
|
||||
exports.ledger = {
|
||||
AccountRoot: [97].concat(sleBase,[
|
||||
['Sequence', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['TransferRate', OPTIONAL],
|
||||
['WalletSize', OPTIONAL],
|
||||
['OwnerCount', REQUIRED],
|
||||
['EmailHash', OPTIONAL],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['AccountTxnID', OPTIONAL],
|
||||
['WalletLocator', OPTIONAL],
|
||||
['Balance', REQUIRED],
|
||||
['MessageKey', OPTIONAL],
|
||||
['Domain', OPTIONAL],
|
||||
['Account', REQUIRED],
|
||||
['RegularKey', OPTIONAL]]),
|
||||
Contract: [99].concat(sleBase,[
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['Expiration', REQUIRED],
|
||||
['BondAmount', REQUIRED],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['Balance', REQUIRED],
|
||||
['FundCode', OPTIONAL],
|
||||
['RemoveCode', OPTIONAL],
|
||||
['ExpireCode', OPTIONAL],
|
||||
['CreateCode', OPTIONAL],
|
||||
['Account', REQUIRED],
|
||||
['Owner', REQUIRED],
|
||||
['Issuer', REQUIRED]]),
|
||||
DirectoryNode: [100].concat(sleBase,[
|
||||
['IndexNext', OPTIONAL],
|
||||
['IndexPrevious', OPTIONAL],
|
||||
['ExchangeRate', OPTIONAL],
|
||||
['RootIndex', REQUIRED],
|
||||
['Owner', OPTIONAL],
|
||||
['TakerPaysCurrency', OPTIONAL],
|
||||
['TakerPaysIssuer', OPTIONAL],
|
||||
['TakerGetsCurrency', OPTIONAL],
|
||||
['TakerGetsIssuer', OPTIONAL],
|
||||
['Indexes', REQUIRED]]),
|
||||
EnabledFeatures: [102].concat(sleBase,[
|
||||
['Features', REQUIRED]]),
|
||||
FeeSettings: [115].concat(sleBase,[
|
||||
['ReferenceFeeUnits', REQUIRED],
|
||||
['ReserveBase', REQUIRED],
|
||||
['ReserveIncrement', REQUIRED],
|
||||
['BaseFee', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL]]),
|
||||
GeneratorMap: [103].concat(sleBase,[
|
||||
['Generator', REQUIRED]]),
|
||||
LedgerHashes: [104].concat(sleBase,[
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['FirstLedgerSequence', OPTIONAL],
|
||||
['LastLedgerSequence', OPTIONAL],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['Hashes', REQUIRED]]),
|
||||
Nickname: [110].concat(sleBase,[
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['MinimumOffer', OPTIONAL],
|
||||
['Account', REQUIRED]]),
|
||||
Offer: [111].concat(sleBase,[
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['Sequence', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['Expiration', OPTIONAL],
|
||||
['BookNode', REQUIRED],
|
||||
['OwnerNode', REQUIRED],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['BookDirectory', REQUIRED],
|
||||
['TakerPays', REQUIRED],
|
||||
['TakerGets', REQUIRED],
|
||||
['Account', REQUIRED]]),
|
||||
RippleState: [114].concat(sleBase,[
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['HighQualityIn', OPTIONAL],
|
||||
['HighQualityOut', OPTIONAL],
|
||||
['LowQualityIn', OPTIONAL],
|
||||
['LowQualityOut', OPTIONAL],
|
||||
['LowNode', OPTIONAL],
|
||||
['HighNode', OPTIONAL],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['Balance', REQUIRED],
|
||||
['LowLimit', REQUIRED],
|
||||
['HighLimit', REQUIRED]])
|
||||
}
|
||||
|
||||
exports.metadata = [
|
||||
[ 'TransactionIndex' , REQUIRED ],
|
||||
[ 'TransactionResult' , REQUIRED ],
|
||||
[ 'AffectedNodes' , REQUIRED ]
|
||||
];
|
||||
|
||||
exports.ter = {
|
||||
tesSUCCESS : 0,
|
||||
tecCLAIM : 100,
|
||||
tecPATH_PARTIAL : 101,
|
||||
tecUNFUNDED_ADD : 102,
|
||||
tecUNFUNDED_OFFER : 103,
|
||||
tecUNFUNDED_PAYMENT : 104,
|
||||
tecFAILED_PROCESSING : 105,
|
||||
tecDIR_FULL : 121,
|
||||
tecINSUF_RESERVE_LINE : 122,
|
||||
tecINSUF_RESERVE_OFFER : 123,
|
||||
tecNO_DST : 124,
|
||||
tecNO_DST_INSUF_XRP : 125,
|
||||
tecNO_LINE_INSUF_RESERVE : 126,
|
||||
tecNO_LINE_REDUNDANT : 127,
|
||||
tecPATH_DRY : 128,
|
||||
tecUNFUNDED : 129, // Deprecated, old ambiguous unfunded.
|
||||
tecMASTER_DISABLED : 130,
|
||||
tecNO_REGULAR_KEY : 131,
|
||||
tecOWNERS : 132,
|
||||
tecNO_ISSUER : 133,
|
||||
tecNO_AUTH : 134,
|
||||
tecNO_LINE : 135,
|
||||
tecINSUFF_FEE : 136,
|
||||
tecFROZEN : 137,
|
||||
tecNO_TARGET : 138,
|
||||
tecNO_PERMISSION : 139,
|
||||
tecNO_ENTRY : 140,
|
||||
tecINSUFFICIENT_RESERVE : 141
|
||||
};
|
||||
|
||||
1584
src/js/ripple/blob.js
Normal file
1584
src/js/ripple/blob.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
// This object serves as a singleton to store config options
|
||||
|
||||
var extend = require("extend");
|
||||
var extend = require('extend');
|
||||
|
||||
var config = module.exports = {
|
||||
load: function (newOpts) {
|
||||
|
||||
333
src/js/ripple/crypt.js
Normal file
333
src/js/ripple/crypt.js
Normal file
@@ -0,0 +1,333 @@
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var base = require('./base').Base;
|
||||
var Seed = require('./seed').Seed;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var request = require('superagent');
|
||||
var querystring = require('querystring');
|
||||
var extend = require("extend");
|
||||
var parser = require("url");
|
||||
var Crypt = { };
|
||||
|
||||
var cryptConfig = {
|
||||
cipher : 'aes',
|
||||
mode : 'ccm',
|
||||
ts : 64, // tag length
|
||||
ks : 256, // key size
|
||||
iter : 1000 // iterations (key derivation)
|
||||
};
|
||||
|
||||
/**
|
||||
* Full domain hash based on SHA512
|
||||
*/
|
||||
|
||||
function fdh(data, bytelen) {
|
||||
var bitlen = bytelen << 3;
|
||||
|
||||
if (typeof data === 'string') {
|
||||
data = sjcl.codec.utf8String.toBits(data);
|
||||
}
|
||||
|
||||
// Add hashing rounds until we exceed desired length in bits
|
||||
var counter = 0, output = [];
|
||||
|
||||
while (sjcl.bitArray.bitLength(output) < bitlen) {
|
||||
var hash = sjcl.hash.sha512.hash(sjcl.bitArray.concat([counter], data));
|
||||
output = sjcl.bitArray.concat(output, hash);
|
||||
counter++;
|
||||
}
|
||||
|
||||
// Truncate to desired length
|
||||
output = sjcl.bitArray.clamp(output, bitlen);
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a function to derive different hashes from the same key.
|
||||
* Each hash is derived as HMAC-SHA512HALF(key, token).
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} hash
|
||||
*/
|
||||
|
||||
function keyHash(key, token) {
|
||||
var hmac = new sjcl.misc.hmac(key, sjcl.hash.sha512);
|
||||
return sjcl.codec.hex.fromBits(sjcl.bitArray.bitSlice(hmac.encrypt(token), 0, 256));
|
||||
};
|
||||
|
||||
/**
|
||||
* add entropy at each call to get random words
|
||||
* @param {number} nWords
|
||||
*/
|
||||
function randomWords (nWords) {
|
||||
for (var i = 0; i < 8; i++) {
|
||||
sjcl.random.addEntropy(Math.random(), 32, "Math.random()");
|
||||
}
|
||||
|
||||
return sjcl.random.randomWords(nWords);
|
||||
}
|
||||
|
||||
/****** exposed functions ******/
|
||||
|
||||
/**
|
||||
* KEY DERIVATION FUNCTION
|
||||
*
|
||||
* This service takes care of the key derivation, i.e. converting low-entropy
|
||||
* secret into higher entropy secret via either computationally expensive
|
||||
* processes or peer-assisted key derivation (PAKDF).
|
||||
*
|
||||
* @param {object} opts
|
||||
* @param {string} purpose - Key type/purpose
|
||||
* @param {string} username
|
||||
* @param {string} secret - Also known as passphrase/password
|
||||
* @param {function} fn
|
||||
*/
|
||||
|
||||
Crypt.derive = function(opts, purpose, username, secret, fn) {
|
||||
var tokens;
|
||||
|
||||
if (purpose === 'login') {
|
||||
tokens = ['id', 'crypt'];
|
||||
} else {
|
||||
tokens = ['unlock'];
|
||||
}
|
||||
|
||||
var iExponent = new sjcl.bn(String(opts.exponent));
|
||||
var iModulus = new sjcl.bn(String(opts.modulus));
|
||||
var iAlpha = new sjcl.bn(String(opts.alpha));
|
||||
|
||||
var publicInfo = [ 'PAKDF_1_0_0', opts.host.length, opts.host, username.length, username, purpose.length, purpose ].join(':') + ':';
|
||||
var publicSize = Math.ceil(Math.min((7 + iModulus.bitLength()) >>> 3, 256) / 8);
|
||||
var publicHash = fdh(publicInfo, publicSize);
|
||||
var publicHex = sjcl.codec.hex.fromBits(publicHash);
|
||||
var iPublic = new sjcl.bn(String(publicHex)).setBitM(0);
|
||||
var secretInfo = [ publicInfo, secret.length, secret ].join(':') + ':';
|
||||
var secretSize = (7 + iModulus.bitLength()) >>> 3;
|
||||
var secretHash = fdh(secretInfo, secretSize);
|
||||
var secretHex = sjcl.codec.hex.fromBits(secretHash);
|
||||
var iSecret = new sjcl.bn(String(secretHex)).mod(iModulus);
|
||||
|
||||
if (iSecret.jacobi(iModulus) !== 1) {
|
||||
iSecret = iSecret.mul(iAlpha).mod(iModulus);
|
||||
}
|
||||
|
||||
var iRandom;
|
||||
|
||||
for (;;) {
|
||||
iRandom = sjcl.bn.random(iModulus, 0);
|
||||
if (iRandom.jacobi(iModulus) === 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var iBlind = iRandom.powermodMontgomery(iPublic.mul(iExponent), iModulus);
|
||||
var iSignreq = iSecret.mulmod(iBlind, iModulus);
|
||||
var signreq = sjcl.codec.hex.fromBits(iSignreq.toBits());
|
||||
|
||||
request.post(opts.url)
|
||||
.send({ info: publicInfo, signreq: signreq })
|
||||
.end(function(err, resp) {
|
||||
if (err || !resp) {
|
||||
return fn(new Error('Could not query PAKDF server ' + opts.host));
|
||||
}
|
||||
|
||||
var data = resp.body || resp.text ? JSON.parse(resp.text) : {};
|
||||
|
||||
if (data.result !== 'success') {
|
||||
return fn(new Error('Could not query PAKDF server '+opts.host));
|
||||
}
|
||||
|
||||
var iSignres = new sjcl.bn(String(data.signres));
|
||||
var iRandomInv = iRandom.inverseMod(iModulus);
|
||||
var iSigned = iSignres.mulmod(iRandomInv, iModulus);
|
||||
var key = iSigned.toBits();
|
||||
var result = { };
|
||||
|
||||
tokens.forEach(function(token) {
|
||||
result[token] = keyHash(key, token);
|
||||
});
|
||||
|
||||
fn(null, result);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Imported from ripple-client
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Encrypt data
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} data
|
||||
*/
|
||||
|
||||
Crypt.encrypt = function(key, data) {
|
||||
key = sjcl.codec.hex.toBits(key);
|
||||
|
||||
var opts = extend(true, {}, cryptConfig);
|
||||
|
||||
var encryptedObj = JSON.parse(sjcl.encrypt(key, data, opts));
|
||||
var version = [sjcl.bitArray.partial(8, 0)];
|
||||
var initVector = sjcl.codec.base64.toBits(encryptedObj.iv);
|
||||
var ciphertext = sjcl.codec.base64.toBits(encryptedObj.ct);
|
||||
|
||||
var encryptedBits = sjcl.bitArray.concat(version, initVector);
|
||||
encryptedBits = sjcl.bitArray.concat(encryptedBits, ciphertext);
|
||||
|
||||
return sjcl.codec.base64.fromBits(encryptedBits);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt data
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} data
|
||||
*/
|
||||
|
||||
Crypt.decrypt = function (key, data) {
|
||||
|
||||
key = sjcl.codec.hex.toBits(key);
|
||||
var encryptedBits = sjcl.codec.base64.toBits(data);
|
||||
|
||||
var version = sjcl.bitArray.extract(encryptedBits, 0, 8);
|
||||
|
||||
if (version !== 0) {
|
||||
throw new Error('Unsupported encryption version: '+version);
|
||||
}
|
||||
|
||||
var encrypted = extend(true, {}, cryptConfig, {
|
||||
iv: sjcl.codec.base64.fromBits(sjcl.bitArray.bitSlice(encryptedBits, 8, 8+128)),
|
||||
ct: sjcl.codec.base64.fromBits(sjcl.bitArray.bitSlice(encryptedBits, 8+128))
|
||||
});
|
||||
|
||||
return sjcl.decrypt(key, JSON.stringify(encrypted));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Validate a ripple address
|
||||
*
|
||||
* @param {string} address
|
||||
*/
|
||||
|
||||
Crypt.isValidAddress = function (address) {
|
||||
return UInt160.is_valid(address);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an encryption key
|
||||
*
|
||||
* @param {integer} nWords - number of words
|
||||
*/
|
||||
|
||||
Crypt.createSecret = function (nWords) {
|
||||
return sjcl.codec.hex.fromBits(randomWords(nWords));
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new master key
|
||||
*/
|
||||
|
||||
Crypt.createMaster = function () {
|
||||
return base.encode_check(33, sjcl.codec.bytes.fromBits(randomWords(4)));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a ripple address from a master key
|
||||
*
|
||||
* @param {string} masterkey
|
||||
*/
|
||||
|
||||
Crypt.getAddress = function (masterkey) {
|
||||
return Seed.from_json(masterkey).get_key().get_address().to_json();
|
||||
};
|
||||
|
||||
/**
|
||||
* Hash data using SHA-512.
|
||||
*
|
||||
* @param {string|bitArray} data
|
||||
* @return {string} Hash of the data
|
||||
*/
|
||||
|
||||
Crypt.hashSha512 = function (data) {
|
||||
// XXX Should return a UInt512
|
||||
return sjcl.codec.hex.fromBits(sjcl.hash.sha512.hash(data));
|
||||
};
|
||||
|
||||
/**
|
||||
* Hash data using SHA-512 and return the first 256 bits.
|
||||
*
|
||||
* @param {string|bitArray} data
|
||||
* @return {UInt256} Hash of the data
|
||||
*/
|
||||
Crypt.hashSha512Half = function (data) {
|
||||
return UInt256.from_hex(Crypt.hashSha512(data).substr(0, 64));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sign a data string with a secret key
|
||||
*
|
||||
* @param {string} secret
|
||||
* @param {string} data
|
||||
*/
|
||||
|
||||
Crypt.signString = function(secret, data) {
|
||||
var hmac = new sjcl.misc.hmac(sjcl.codec.hex.toBits(secret), sjcl.hash.sha512);
|
||||
return sjcl.codec.hex.fromBits(hmac.mac(data));
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an an accout recovery key
|
||||
*
|
||||
* @param {string} secret
|
||||
*/
|
||||
|
||||
Crypt.deriveRecoveryEncryptionKeyFromSecret = function(secret) {
|
||||
var seed = Seed.from_json(secret).to_bits();
|
||||
var hmac = new sjcl.misc.hmac(seed, sjcl.hash.sha512);
|
||||
var key = hmac.mac('ripple/hmac/recovery_encryption_key/v1');
|
||||
key = sjcl.bitArray.bitSlice(key, 0, 256);
|
||||
return sjcl.codec.hex.fromBits(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert base64 encoded data into base64url encoded data.
|
||||
*
|
||||
* @param {String} base64 Data
|
||||
*/
|
||||
|
||||
Crypt.base64ToBase64Url = function(encodedData) {
|
||||
return encodedData.replace(/\+/g, '-').replace(/\//g, '_').replace(/[=]+$/, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert base64url encoded data into base64 encoded data.
|
||||
*
|
||||
* @param {String} base64 Data
|
||||
*/
|
||||
|
||||
Crypt.base64UrlToBase64 = function(encodedData) {
|
||||
encodedData = encodedData.replace(/-/g, '+').replace(/_/g, '/');
|
||||
|
||||
while (encodedData.length % 4) {
|
||||
encodedData += '=';
|
||||
}
|
||||
|
||||
return encodedData;
|
||||
};
|
||||
|
||||
/**
|
||||
* base64 to UTF8
|
||||
*/
|
||||
|
||||
Crypt.decodeBase64 = function (data) {
|
||||
return sjcl.codec.utf8String.fromBits(sjcl.codec.base64.toBits(data));
|
||||
}
|
||||
|
||||
exports.Crypt = Crypt;
|
||||
@@ -1,10 +1,13 @@
|
||||
var extend = require('extend');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var utils = require('./utils');
|
||||
var Float = require('./ieee754').Float;
|
||||
|
||||
//
|
||||
// Currency support
|
||||
//
|
||||
|
||||
// XXX Internal form should be UInt160.
|
||||
var Currency = function () {
|
||||
var Currency = extend(function() {
|
||||
// Internal form: 0 = XRP. 3 letter-code.
|
||||
// XXX Internal should be 0 or hex with three letter annotation when valid.
|
||||
|
||||
@@ -14,87 +17,229 @@ var Currency = function () {
|
||||
// XXX Should support hex, C++ doesn't currently allow it.
|
||||
|
||||
this._value = NaN;
|
||||
|
||||
this._update();
|
||||
}, UInt160);
|
||||
|
||||
Currency.prototype = extend({}, UInt160.prototype);
|
||||
Currency.prototype.constructor = Currency;
|
||||
|
||||
Currency.HEX_CURRENCY_BAD = '0000000000000000000000005852500000000000';
|
||||
|
||||
/**
|
||||
* Tries to correctly interpret a Currency as entered by a user.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* USD => currency
|
||||
* USD - Dollar => currency with optional full currency name
|
||||
* XAU (-0.5%pa) => XAU with 0.5% effective demurrage rate per year
|
||||
* XAU - Gold (-0.5%pa) => Optionally allowed full currency name
|
||||
* USD (1%pa) => US dollars with 1% effective interest per year
|
||||
* INR - Indian Rupees => Optional full currency name with spaces
|
||||
* TYX - 30-Year Treasuries => Optional full currency with numbers and a dash
|
||||
* TYX - 30-Year Treasuries (1.5%pa) => Optional full currency with numbers, dash and interest rate
|
||||
*
|
||||
* The regular expression below matches above cases, broken down for better understanding:
|
||||
*
|
||||
* ^\s* // start with any amount of whitespace
|
||||
* ([a-zA-Z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3 digit numeric currency-code. See ISO 4217
|
||||
* (\s*-\s*[- \w]+) // optional full currency name following the dash after currency code,
|
||||
* full currency code can contain letters, numbers and dashes
|
||||
* (\s*\(-?\d+\.?\d*%pa\))? // optional demurrage rate, has optional - and . notation (-0.5%pa)
|
||||
* \s*$ // end with any amount of whitespace
|
||||
*
|
||||
*/
|
||||
Currency.prototype.human_RE = /^\s*([a-zA-Z0-9]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/;
|
||||
|
||||
Currency.from_json = function(j, shouldInterpretXrpAsIou) {
|
||||
return (new Currency()).parse_json(j, shouldInterpretXrpAsIou);
|
||||
};
|
||||
|
||||
Currency.from_human = function(j, opts) {
|
||||
return (new Currency().parse_human(j, opts));
|
||||
}
|
||||
|
||||
// Given "USD" return the json.
|
||||
Currency.json_rewrite = function (j) {
|
||||
return Currency.from_json(j).to_json();
|
||||
};
|
||||
|
||||
Currency.from_json = function (j) {
|
||||
if (j instanceof Currency) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return new Currency().parse_json(j);
|
||||
}
|
||||
};
|
||||
|
||||
Currency.from_bytes = function (j) {
|
||||
if (j instanceof Currency) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return new Currency().parse_bytes(j);
|
||||
}
|
||||
};
|
||||
|
||||
Currency.is_valid = function (j) {
|
||||
return Currency.from_json(j).is_valid();
|
||||
};
|
||||
|
||||
Currency.prototype.clone = function() {
|
||||
return this.copyTo(new Currency());
|
||||
};
|
||||
|
||||
// Returns copy.
|
||||
Currency.prototype.copyTo = function (d) {
|
||||
d._value = this._value;
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
Currency.prototype.equals = function (d) {
|
||||
return ('string' !== typeof this._value && isNaN(this._value))
|
||||
|| ('string' !== typeof d._value && isNaN(d._value)) ? false : this._value === d._value;
|
||||
};
|
||||
|
||||
// this._value = NaN on error.
|
||||
Currency.prototype.parse_json = function (j) {
|
||||
if (j instanceof Currency) {
|
||||
this._value = j;
|
||||
} else if ('string' === typeof j) {
|
||||
if (j === "" || j === "0" || j === "XRP") {
|
||||
// XRP is never allowed as a Currency object
|
||||
this._value = 0;
|
||||
} else if (j.length === 3) {
|
||||
this._value = j;
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
} else if ('number' === typeof j) {
|
||||
// XXX This is a hack
|
||||
this._value = j;
|
||||
} else if ('string' != typeof j || 3 !== j.length) {
|
||||
this._value = NaN;
|
||||
} else {
|
||||
this._value = j;
|
||||
Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
|
||||
this._value = NaN;
|
||||
|
||||
switch (typeof j) {
|
||||
case 'string':
|
||||
|
||||
// if an empty string is given, fall back to XRP
|
||||
if (!j) {
|
||||
this.parse_hex(shouldInterpretXrpAsIou ? Currency.HEX_CURRENCY_BAD : Currency.HEX_ZERO);
|
||||
break;
|
||||
}
|
||||
|
||||
// match the given string to see if it's in an allowed format
|
||||
var matches = String(j).match(this.human_RE);
|
||||
|
||||
if (matches) {
|
||||
|
||||
var currencyCode = matches[1];
|
||||
|
||||
// for the currency 'XRP' case
|
||||
// we drop everything else that could have been provided
|
||||
// e.g. 'XRP - Ripple'
|
||||
if (!currencyCode || /^(0|XRP)$/.test(currencyCode)) {
|
||||
this.parse_hex(shouldInterpretXrpAsIou ? Currency.HEX_CURRENCY_BAD : Currency.HEX_ZERO);
|
||||
|
||||
// early break, we can't have interest on XRP
|
||||
break;
|
||||
}
|
||||
|
||||
// the full currency is matched as it is part of the valid currency format, but not stored
|
||||
// var full_currency = matches[2] || '';
|
||||
var interest = matches[3] || '';
|
||||
|
||||
// interest is defined as interest per year, per annum (pa)
|
||||
var percentage = interest.match(/(-?\d+\.?\d+)/);
|
||||
|
||||
currencyCode = currencyCode.toUpperCase();
|
||||
|
||||
var currencyData = utils.arraySet(20, 0);
|
||||
|
||||
if (percentage) {
|
||||
/*
|
||||
* 20 byte layout of a interest bearing currency
|
||||
*
|
||||
* 01 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __
|
||||
* CURCODE- DATE------- RATE------------------- RESERVED---
|
||||
*/
|
||||
|
||||
// byte 1 for type, use '1' to denote demurrage currency
|
||||
currencyData[0] = 1;
|
||||
|
||||
// byte 2-4 for currency code
|
||||
currencyData[1] = currencyCode.charCodeAt(0) & 0xff;
|
||||
currencyData[2] = currencyCode.charCodeAt(1) & 0xff;
|
||||
currencyData[3] = currencyCode.charCodeAt(2) & 0xff;
|
||||
|
||||
// byte 5-8 are for reference date, but should always be 0 so we won't fill it
|
||||
|
||||
// byte 9-16 are for the interest
|
||||
percentage = parseFloat(percentage[0]);
|
||||
|
||||
// the interest or demurrage is expressed as a yearly (per annum) value
|
||||
var secondsPerYear = 31536000; // 60 * 60 * 24 * 365
|
||||
|
||||
// Calculating the interest e-fold
|
||||
// 0.5% demurrage is expressed 0.995, 0.005 less than 1
|
||||
// 0.5% interest is expressed as 1.005, 0.005 more than 1
|
||||
var interestEfold = secondsPerYear / Math.log(1 + percentage/100);
|
||||
var bytes = Float.toIEEE754Double(interestEfold);
|
||||
|
||||
for (var i=0; i<=bytes.length; i++) {
|
||||
currencyData[8 + i] = bytes[i] & 0xff;
|
||||
}
|
||||
|
||||
// the last 4 bytes are reserved for future use, so we won't fill those
|
||||
|
||||
} else {
|
||||
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
|
||||
currencyData[13] = currencyCode.charCodeAt(1) & 0xff;
|
||||
currencyData[14] = currencyCode.charCodeAt(2) & 0xff;
|
||||
}
|
||||
|
||||
this.parse_bytes(currencyData);
|
||||
} else {
|
||||
this.parse_hex(j);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
if (!isNaN(j)) {
|
||||
this.parse_number(j);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
if (j instanceof Currency) {
|
||||
this._value = j.copyTo({})._value;
|
||||
this._update();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Currency.prototype.parse_bytes = function (byte_array) {
|
||||
if (Array.isArray(byte_array) && byte_array.length == 20) {
|
||||
|
||||
Currency.prototype.parse_human = function(j) {
|
||||
return this.parse_json(j);
|
||||
};
|
||||
|
||||
/**
|
||||
* Recalculate internal representation.
|
||||
*
|
||||
* You should never need to call this.
|
||||
*/
|
||||
Currency.prototype._update = function() {
|
||||
var bytes = this.to_bytes();
|
||||
|
||||
// is it 0 everywhere except 12, 13, 14?
|
||||
var isZeroExceptInStandardPositions = true;
|
||||
|
||||
if (!bytes) {
|
||||
return 'XRP';
|
||||
}
|
||||
|
||||
this._native = false;
|
||||
this._type = -1;
|
||||
this._interest_start = NaN;
|
||||
this._interest_period = NaN;
|
||||
this._iso_code = '';
|
||||
|
||||
for (var i=0; i<20; i++) {
|
||||
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || bytes[i]===0);
|
||||
}
|
||||
|
||||
if (isZeroExceptInStandardPositions) {
|
||||
this._iso_code = String.fromCharCode(bytes[12])
|
||||
+ String.fromCharCode(bytes[13])
|
||||
+ String.fromCharCode(bytes[14]);
|
||||
|
||||
if (this._iso_code === '\0\0\0') {
|
||||
this._native = true;
|
||||
this._iso_code = 'XRP';
|
||||
}
|
||||
|
||||
this._type = 0;
|
||||
} else if (bytes[0] === 0x01) { // Demurrage currency
|
||||
this._iso_code = String.fromCharCode(bytes[1])
|
||||
+ String.fromCharCode(bytes[2])
|
||||
+ String.fromCharCode(bytes[3]);
|
||||
|
||||
this._type = 1;
|
||||
this._interest_start = (bytes[4] << 24) +
|
||||
(bytes[5] << 16) +
|
||||
(bytes[6] << 8) +
|
||||
(bytes[7] );
|
||||
this._interest_period = Float.fromIEEE754Double(bytes.slice(8, 16));
|
||||
}
|
||||
};
|
||||
|
||||
// XXX Probably not needed anymore?
|
||||
/*
|
||||
Currency.prototype.parse_bytes = function(byte_array) {
|
||||
if (Array.isArray(byte_array) && byte_array.length === 20) {
|
||||
var result;
|
||||
// is it 0 everywhere except 12, 13, 14?
|
||||
var isZeroExceptInStandardPositions = true;
|
||||
|
||||
for (var i=0; i<20; i++) {
|
||||
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || byte_array[0]===0)
|
||||
}
|
||||
|
||||
if (isZeroExceptInStandardPositions) {
|
||||
var currencyCode = String.fromCharCode(byte_array[12]) + String.fromCharCode(byte_array[13]) + String.fromCharCode(byte_array[14]);
|
||||
if (/^[A-Z]{3}$/.test(currencyCode) && currencyCode !== "XRP" ) {
|
||||
var currencyCode = String.fromCharCode(byte_array[12])
|
||||
+ String.fromCharCode(byte_array[13])
|
||||
+ String.fromCharCode(byte_array[14]);
|
||||
if (/^[A-Z0-9]{3}$/.test(currencyCode) && currencyCode !== 'XRP' ) {
|
||||
this._value = currencyCode;
|
||||
} else if (currencyCode === "\0\0\0") {
|
||||
} else if (currencyCode === '\0\0\0') {
|
||||
this._value = 0;
|
||||
} else {
|
||||
this._value = NaN;
|
||||
@@ -108,21 +253,107 @@ Currency.prototype.parse_bytes = function (byte_array) {
|
||||
}
|
||||
return this;
|
||||
};
|
||||
*/
|
||||
|
||||
Currency.prototype.is_native = function () {
|
||||
return !isNaN(this._value) && !this._value;
|
||||
Currency.prototype.is_native = function() {
|
||||
return this._native;
|
||||
};
|
||||
|
||||
Currency.prototype.is_valid = function () {
|
||||
return 'string' === typeof this._value || !isNaN(this._value);
|
||||
/**
|
||||
* Whether this currency is an interest-bearing/demurring currency.
|
||||
*/
|
||||
Currency.prototype.has_interest = function() {
|
||||
return this._type === 1 && !isNaN(this._interest_start) && !isNaN(this._interest_period);
|
||||
};
|
||||
|
||||
Currency.prototype.to_json = function () {
|
||||
return this._value ? this._value : "XRP";
|
||||
/**
|
||||
*
|
||||
* @param referenceDate - number of seconds since the Ripple Epoch (0:00 on January 1, 2000 UTC)
|
||||
* used to calculate the interest over provided interval
|
||||
* pass in one years worth of seconds to ge the yearly interest
|
||||
* @returns {number} - interest for provided interval, can be negative for demurred currencies
|
||||
*/
|
||||
Currency.prototype.get_interest_at = function(referenceDate, decimals) {
|
||||
if (!this.has_interest()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// use one year as a default period
|
||||
if (!referenceDate) {
|
||||
referenceDate = this._interest_start + 3600 * 24 * 365;
|
||||
}
|
||||
|
||||
if (referenceDate instanceof Date) {
|
||||
referenceDate = utils.fromTimestamp(referenceDate.getTime());
|
||||
}
|
||||
|
||||
// calculate interest by e-fold number
|
||||
return Math.exp((referenceDate - this._interest_start) / this._interest_period);
|
||||
};
|
||||
|
||||
Currency.prototype.to_human = function () {
|
||||
return this._value ? this._value : "XRP";
|
||||
Currency.prototype.get_interest_percentage_at = function(referenceDate, decimals) {
|
||||
var interest = this.get_interest_at(referenceDate, decimals);
|
||||
|
||||
// convert to percentage
|
||||
var interest = (interest*100)-100;
|
||||
var decimalMultiplier = decimals ? Math.pow(10,decimals) : 100;
|
||||
|
||||
// round to two decimals behind the dot
|
||||
return Math.round(interest*decimalMultiplier) / decimalMultiplier;
|
||||
};
|
||||
|
||||
// XXX Currently we inherit UInt.prototype.is_valid, which is mostly fine.
|
||||
//
|
||||
// We could be doing further checks into the internal format of the
|
||||
// currency data, since there are some values that are invalid.
|
||||
//
|
||||
//Currency.prototype.is_valid = function() {
|
||||
// return this._value instanceof BigInteger && ...;
|
||||
//};
|
||||
|
||||
Currency.prototype.to_json = function(opts) {
|
||||
if (!this.is_valid()) {
|
||||
// XXX This is backwards compatible behavior, but probably not very good.
|
||||
return 'XRP';
|
||||
}
|
||||
|
||||
var opts = opts || {};
|
||||
|
||||
var currency;
|
||||
var fullName = opts && opts.full_name ? ' - ' + opts.full_name : '';
|
||||
opts.show_interest = opts.show_interest !== void(0) ? opts.show_interest : this.has_interest();
|
||||
|
||||
if (!opts.force_hex && /^[A-Z0-9]{3}$/.test(this._iso_code)) {
|
||||
currency = this._iso_code + fullName;
|
||||
if (opts.show_interest) {
|
||||
var decimals = !isNaN(opts.decimals) ? opts.decimals : void(0);
|
||||
var interestPercentage = this.has_interest() ? this.get_interest_percentage_at(this._interest_start + 3600 * 24 * 365, decimals) : 0;
|
||||
currency += ' (' + interestPercentage + '%pa)';
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Fallback to returning the raw currency hex
|
||||
currency = this.to_hex();
|
||||
|
||||
// XXX This is to maintain backwards compatibility, but it is very, very odd
|
||||
// behavior, so we should deprecate it and get rid of it as soon as
|
||||
// possible.
|
||||
if (currency === Currency.HEX_ONE) {
|
||||
currency = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return currency;
|
||||
};
|
||||
|
||||
Currency.prototype.to_human = function(opts) {
|
||||
// to_human() will always print the human-readable currency code if available.
|
||||
return this.to_json(opts);
|
||||
};
|
||||
|
||||
Currency.prototype.get_iso = function() {
|
||||
return this._iso_code;
|
||||
};
|
||||
|
||||
exports.Currency = Currency;
|
||||
|
||||
59
src/js/ripple/float.js
Normal file
59
src/js/ripple/float.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* IEEE 754 floating-point.
|
||||
*
|
||||
* Supports single- or double-precision
|
||||
*/
|
||||
var Float = exports.Float = {};
|
||||
|
||||
var allZeros = /^0+$/;
|
||||
var allOnes = /^1+$/;
|
||||
|
||||
Float.fromBytes = function(bytes) {
|
||||
// Render in binary. Hackish.
|
||||
var b = '';
|
||||
|
||||
for (var i = 0, n = bytes.length; i < n; i++) {
|
||||
var bits = (bytes[i] & 0xff).toString(2);
|
||||
|
||||
while (bits.length < 8) {
|
||||
bits = '0' + bits;
|
||||
}
|
||||
|
||||
b += bits;
|
||||
}
|
||||
|
||||
// Determine configuration. This could have all been precomputed but it is fast enough.
|
||||
var exponentBits = bytes.length === 4 ? 4 : 11;
|
||||
var mantissaBits = (bytes.length * 8) - exponentBits - 1;
|
||||
var bias = Math.pow(2, exponentBits - 1) - 1;
|
||||
var minExponent = 1 - bias - mantissaBits;
|
||||
|
||||
// Break up the binary representation into its pieces for easier processing.
|
||||
var s = b[0];
|
||||
var e = b.substring(1, exponentBits + 1);
|
||||
var m = b.substring(exponentBits + 1);
|
||||
|
||||
var value = 0;
|
||||
var multiplier = (s === '0' ? 1 : -1);
|
||||
|
||||
if (allZeros.test(e)) {
|
||||
// Zero or denormalized
|
||||
if (!allZeros.test(m)) {
|
||||
value = parseInt(m, 2) * Math.pow(2, minExponent);
|
||||
}
|
||||
} else if (allOnes.test(e)) {
|
||||
// Infinity or NaN
|
||||
if (allZeros.test(m)) {
|
||||
value = Infinity;
|
||||
} else {
|
||||
value = NaN;
|
||||
}
|
||||
} else {
|
||||
// Normalized
|
||||
var exponent = parseInt(e, 2) - bias;
|
||||
var mantissa = parseInt(m, 2);
|
||||
value = (1 + (mantissa * Math.pow(2, -mantissaBits))) * Math.pow(2, exponent);
|
||||
}
|
||||
|
||||
return value * multiplier;
|
||||
};
|
||||
25
src/js/ripple/hashprefixes.js
Normal file
25
src/js/ripple/hashprefixes.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Prefix for hashing functions.
|
||||
*
|
||||
* These prefixes are inserted before the source material used to
|
||||
* generate various hashes. This is done to put each hash in its own
|
||||
* "space." This way, two different types of objects with the
|
||||
* same binary data will produce different hashes.
|
||||
*
|
||||
* Each prefix is a 4-byte value with the last byte set to zero
|
||||
* and the first three bytes formed from the ASCII equivalent of
|
||||
* some arbitrary string. For example "TXN".
|
||||
*/
|
||||
|
||||
// transaction plus signature to give transaction ID
|
||||
exports.HASH_TX_ID = 0x54584E00; // 'TXN'
|
||||
// transaction plus metadata
|
||||
exports.HASH_TX_NODE = 0x534E4400; // 'TND'
|
||||
// inner node in tree
|
||||
exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN'
|
||||
// leaf node in tree
|
||||
exports.HASH_LEAF_NODE = 0x4D4C4E00; // 'MLN'
|
||||
// inner transaction to sign
|
||||
exports.HASH_TX_SIGN = 0x53545800; // 'STX'
|
||||
// inner transaction to sign (TESTNET)
|
||||
exports.HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'
|
||||
107
src/js/ripple/ieee754.js
Normal file
107
src/js/ripple/ieee754.js
Normal file
@@ -0,0 +1,107 @@
|
||||
// Convert a JavaScript number to IEEE-754 Double Precision
|
||||
// value represented as an array of 8 bytes (octets)
|
||||
//
|
||||
// Based on:
|
||||
// http://cautionsingularityahead.blogspot.com/2010/04/javascript-and-ieee754-redux.html
|
||||
//
|
||||
// Found and modified from:
|
||||
// https://gist.github.com/bartaz/1119041
|
||||
|
||||
var Float = exports.Float = {};
|
||||
|
||||
Float.toIEEE754 = function(v, ebits, fbits) {
|
||||
|
||||
var bias = (1 << (ebits - 1)) - 1;
|
||||
|
||||
// Compute sign, exponent, fraction
|
||||
var s, e, f;
|
||||
if (isNaN(v)) {
|
||||
e = (1 << bias) - 1; f = 1; s = 0;
|
||||
}
|
||||
else if (v === Infinity || v === -Infinity) {
|
||||
e = (1 << bias) - 1; f = 0; s = (v < 0) ? 1 : 0;
|
||||
}
|
||||
else if (v === 0) {
|
||||
e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0;
|
||||
}
|
||||
else {
|
||||
s = v < 0;
|
||||
v = Math.abs(v);
|
||||
|
||||
if (v >= Math.pow(2, 1 - bias)) {
|
||||
var ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
|
||||
e = ln + bias;
|
||||
f = v * Math.pow(2, fbits - ln) - Math.pow(2, fbits);
|
||||
}
|
||||
else {
|
||||
e = 0;
|
||||
f = v / Math.pow(2, 1 - bias - fbits);
|
||||
}
|
||||
}
|
||||
|
||||
// Pack sign, exponent, fraction
|
||||
var i, bits = [];
|
||||
for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = Math.floor(f / 2); }
|
||||
for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = Math.floor(e / 2); }
|
||||
bits.push(s ? 1 : 0);
|
||||
bits.reverse();
|
||||
var str = bits.join('');
|
||||
|
||||
// Bits to bytes
|
||||
var bytes = [];
|
||||
while (str.length) {
|
||||
bytes.push(parseInt(str.substring(0, 8), 2));
|
||||
str = str.substring(8);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Float.fromIEEE754 = function(bytes, ebits, fbits) {
|
||||
|
||||
// Bytes to bits
|
||||
var bits = [];
|
||||
for (var i = bytes.length; i; i -= 1) {
|
||||
var byte = bytes[i - 1];
|
||||
for (var j = 8; j; j -= 1) {
|
||||
bits.push(byte % 2 ? 1 : 0); byte = byte >> 1;
|
||||
}
|
||||
}
|
||||
bits.reverse();
|
||||
var str = bits.join('');
|
||||
|
||||
// Unpack sign, exponent, fraction
|
||||
var bias = (1 << (ebits - 1)) - 1;
|
||||
var s = parseInt(str.substring(0, 1), 2) ? -1 : 1;
|
||||
var e = parseInt(str.substring(1, 1 + ebits), 2);
|
||||
var f = parseInt(str.substring(1 + ebits), 2);
|
||||
|
||||
// Produce number
|
||||
if (e === (1 << ebits) - 1) {
|
||||
return f !== 0 ? NaN : s * Infinity;
|
||||
}
|
||||
else if (e > 0) {
|
||||
return s * Math.pow(2, e - bias) * (1 + f / Math.pow(2, fbits));
|
||||
}
|
||||
else if (f !== 0) {
|
||||
return s * Math.pow(2, -(bias-1)) * (f / Math.pow(2, fbits));
|
||||
}
|
||||
else {
|
||||
return s * 0;
|
||||
}
|
||||
}
|
||||
|
||||
Float.fromIEEE754Double = function(b) { return Float.fromIEEE754(b, 11, 52); }
|
||||
Float.toIEEE754Double = function(v) { return Float.toIEEE754(v, 11, 52); }
|
||||
Float.fromIEEE754Single = function(b) { return Float.fromIEEE754(b, 8, 23); }
|
||||
Float.toIEEE754Single = function(v) { return Float.toIEEE754(v, 8, 23); }
|
||||
|
||||
|
||||
// Convert array of octets to string binary representation
|
||||
// by bartaz
|
||||
|
||||
Float.toIEEE754DoubleString = function(v) {
|
||||
return exports.toIEEE754Double(v)
|
||||
.map(function(n){ for(n = n.toString(2);n.length < 8;n="0"+n); return n })
|
||||
.join('')
|
||||
.replace(/(.)(.{11})(.{52})/, "$1 $2 $3")
|
||||
}
|
||||
@@ -1,15 +1,24 @@
|
||||
exports.Remote = require('./remote').Remote;
|
||||
exports.Amount = require('./amount').Amount;
|
||||
exports.Currency = require('./currency').Currency;
|
||||
exports.Base = require('./base').Base;
|
||||
exports.UInt160 = require('./amount').UInt160;
|
||||
exports.Seed = require('./amount').Seed;
|
||||
exports.Transaction = require('./transaction').Transaction;
|
||||
exports.Meta = require('./meta').Meta;
|
||||
exports.Remote = require('./remote').Remote;
|
||||
exports.Request = require('./request').Request;
|
||||
exports.Amount = require('./amount').Amount;
|
||||
exports.Account = require('./account').Account;
|
||||
exports.Transaction = require('./transaction').Transaction;
|
||||
exports.Currency = require('./currency').Currency;
|
||||
exports.Base = require('./base').Base;
|
||||
exports.UInt160 = require('./uint160').UInt160;
|
||||
exports.UInt256 = require('./uint256').UInt256;
|
||||
exports.Seed = require('./seed').Seed;
|
||||
exports.Meta = require('./meta').Meta;
|
||||
exports.SerializedObject = require('./serializedobject').SerializedObject;
|
||||
|
||||
exports.binformat = require('./binformat');
|
||||
exports.utils = require('./utils');
|
||||
exports.RippleError = require('./rippleerror').RippleError;
|
||||
exports.Message = require('./message').Message;
|
||||
exports.VaultClient = require('./vaultclient').VaultClient;
|
||||
exports.AuthInfo = require('./authinfo').AuthInfo;
|
||||
exports.RippleTxt = require('./rippletxt').RippleTxt;
|
||||
exports.binformat = require('./binformat');
|
||||
exports.utils = require('./utils');
|
||||
exports.Server = require('./server').Server;
|
||||
exports.Wallet = require('./wallet');
|
||||
|
||||
// Important: We do not guarantee any specific version of SJCL or for any
|
||||
// specific features to be included. The version and configuration may change at
|
||||
@@ -18,8 +27,34 @@ exports.utils = require('./utils');
|
||||
// However, for programs that are tied to a specific version of ripple.js like
|
||||
// the official client, it makes sense to expose the SJCL instance so we don't
|
||||
// have to include it twice.
|
||||
exports.sjcl = require('../../../build/sjcl');
|
||||
exports.sjcl = require('./utils').sjcl;
|
||||
|
||||
exports.config = require('./config');
|
||||
exports.config = require('./config');
|
||||
|
||||
// camelCase to under_scored API conversion
|
||||
function attachUnderscored(c) {
|
||||
var o = exports[c];
|
||||
|
||||
Object.keys(o.prototype).forEach(function(key) {
|
||||
var UPPERCASE = /([A-Z]{1})[a-z]+/g;
|
||||
|
||||
if (!UPPERCASE.test(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var underscored = key.replace(UPPERCASE, function(c) {
|
||||
return '_' + c.toLowerCase();
|
||||
});
|
||||
|
||||
o.prototype[underscored] = o.prototype[key];
|
||||
});
|
||||
};
|
||||
|
||||
[ 'Remote',
|
||||
'Request',
|
||||
'Transaction',
|
||||
'Account',
|
||||
'Server'
|
||||
].forEach(attachUnderscored);
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var sjcl = require('./utils').sjcl;
|
||||
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var Base = require('./base').Base;
|
||||
|
||||
var KeyPair = function ()
|
||||
{
|
||||
this._curve = sjcl.ecc.curves['c256'];
|
||||
function KeyPair() {
|
||||
this._curve = sjcl.ecc.curves.c256;
|
||||
this._secret = null;
|
||||
this._pubkey = null;
|
||||
};
|
||||
|
||||
KeyPair.from_bn_secret = function (j)
|
||||
{
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return (new this()).parse_bn_secret(j);
|
||||
}
|
||||
KeyPair.from_bn_secret = function(j) {
|
||||
return (j instanceof this) ? j.clone() : (new this()).parse_bn_secret(j);
|
||||
};
|
||||
|
||||
KeyPair.prototype.parse_bn_secret = function (j)
|
||||
{
|
||||
this._secret = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves['c256'], j);
|
||||
KeyPair.prototype.parse_bn_secret = function(j) {
|
||||
this._secret = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves.c256, j);
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -29,8 +24,7 @@ KeyPair.prototype.parse_bn_secret = function (j)
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
KeyPair.prototype._pub = function ()
|
||||
{
|
||||
KeyPair.prototype._pub = function() {
|
||||
var curve = this._curve;
|
||||
|
||||
if (!this._pubkey && this._secret) {
|
||||
@@ -41,27 +35,64 @@ KeyPair.prototype._pub = function ()
|
||||
return this._pubkey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns public key in compressed format as bit array.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
KeyPair.prototype._pub_bits = function() {
|
||||
var pub = this._pub();
|
||||
|
||||
if (!pub) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var point = pub._point, y_even = point.y.mod(2).equals(0);
|
||||
|
||||
return sjcl.bitArray.concat(
|
||||
[sjcl.bitArray.partial(8, y_even ? 0x02 : 0x03)],
|
||||
point.x.toBits(this._curve.r.bitLength())
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns public key as hex.
|
||||
*
|
||||
* Key will be returned as a compressed pubkey - 33 bytes converted to hex.
|
||||
*/
|
||||
KeyPair.prototype.to_hex_pub = function ()
|
||||
{
|
||||
var pub = this._pub();
|
||||
if (!pub) return null;
|
||||
KeyPair.prototype.to_hex_pub = function() {
|
||||
var bits = this._pub_bits();
|
||||
|
||||
var point = pub._point, y_even = point.y.mod(2).equals(0);
|
||||
return sjcl.codec.hex.fromBits(sjcl.bitArray.concat(
|
||||
[sjcl.bitArray.partial(8, y_even ? 0x02 : 0x03)],
|
||||
point.x.toBits(this._curve.r.bitLength())
|
||||
)).toUpperCase();
|
||||
if (!bits) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sjcl.codec.hex.fromBits(bits).toUpperCase();
|
||||
};
|
||||
|
||||
KeyPair.prototype.sign = function (hash)
|
||||
{
|
||||
function SHA256_RIPEMD160(bits) {
|
||||
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
||||
}
|
||||
|
||||
KeyPair.prototype.get_address = function() {
|
||||
var bits = this._pub_bits();
|
||||
|
||||
if (!bits) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var hash = SHA256_RIPEMD160(bits);
|
||||
|
||||
var address = UInt160.from_bits(hash);
|
||||
address.set_version(Base.VER_ACCOUNT_ID);
|
||||
return address;
|
||||
};
|
||||
|
||||
KeyPair.prototype.sign = function(hash) {
|
||||
hash = UInt256.from_json(hash);
|
||||
return this._secret.signDER(hash.to_bits(), 0);
|
||||
var sig = this._secret.sign(hash.to_bits(), 0);
|
||||
sig = this._secret.canonicalizeSignature(sig);
|
||||
return this._secret.encodeDER(sig);
|
||||
};
|
||||
|
||||
exports.KeyPair = KeyPair;
|
||||
|
||||
167
src/js/ripple/ledger.js
Normal file
167
src/js/ripple/ledger.js
Normal file
@@ -0,0 +1,167 @@
|
||||
// Ledger
|
||||
|
||||
var Transaction = require('./transaction').Transaction;
|
||||
var SHAMap = require('./shamap').SHAMap;
|
||||
var SHAMapTreeNode = require('./shamap').SHAMapTreeNode;
|
||||
var SerializedObject = require('./serializedobject').SerializedObject;
|
||||
var stypes = require('./serializedtypes');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Currency = require('./currency').Currency;
|
||||
var stypes = require('./serializedtypes');
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var Crypt = require('./crypt').Crypt;
|
||||
|
||||
function Ledger()
|
||||
{
|
||||
this.ledger_json = {};
|
||||
}
|
||||
|
||||
Ledger.from_json = function (v) {
|
||||
var ledger = new Ledger();
|
||||
ledger.parse_json(v);
|
||||
return ledger;
|
||||
};
|
||||
|
||||
Ledger.space = require('./ledgerspaces');
|
||||
|
||||
/**
|
||||
* Generate the key for an AccountRoot entry.
|
||||
*
|
||||
* @param {String|UInt160} account Ripple Account
|
||||
* @return {UInt256}
|
||||
*/
|
||||
Ledger.calcAccountRootEntryHash =
|
||||
Ledger.prototype.calcAccountRootEntryHash = function (account) {
|
||||
account = UInt160.from_json(account);
|
||||
|
||||
var index = new SerializedObject();
|
||||
|
||||
index.append([0, Ledger.space.account.charCodeAt(0)]);
|
||||
index.append(account.to_bytes());
|
||||
|
||||
return index.hash();
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate the key for an Offer entry.
|
||||
*
|
||||
* @param {String|UInt160} account Ripple Account
|
||||
* @param {Number} sequence Sequence number of the OfferCreate transaction
|
||||
* that instantiated this offer.
|
||||
* @return {UInt256}
|
||||
*/
|
||||
Ledger.calcOfferEntryHash =
|
||||
Ledger.prototype.calcOfferEntryHash = function (account, sequence) {
|
||||
account = UInt160.from_json(account);
|
||||
sequence = parseInt(sequence);
|
||||
|
||||
var index = new SerializedObject();
|
||||
|
||||
index.append([0, Ledger.space.offer.charCodeAt(0)]);
|
||||
index.append(account.to_bytes());
|
||||
stypes.Int32.serialize(index, sequence);
|
||||
|
||||
return index.hash();
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate the key for a RippleState entry.
|
||||
*
|
||||
* The ordering of the two account parameters does not matter.
|
||||
*
|
||||
* @param {String|UInt160} account1 First Ripple Account
|
||||
* @param {String|UInt160} account2 Second Ripple Account
|
||||
* @param {String|Currency} currency The currency code
|
||||
* @return {UInt256}
|
||||
*/
|
||||
Ledger.calcRippleStateEntryHash =
|
||||
Ledger.prototype.calcRippleStateEntryHash = function (account1, account2, currency) {
|
||||
currency = Currency.from_json(currency);
|
||||
account1 = UInt160.from_json(account1);
|
||||
account2 = UInt160.from_json(account2);
|
||||
|
||||
if (!account1.is_valid()) {
|
||||
throw new Error("Invalid first account");
|
||||
}
|
||||
if (!account2.is_valid()) {
|
||||
throw new Error("Invalid second account");
|
||||
}
|
||||
if (!currency.is_valid()) {
|
||||
throw new Error("Invalid currency");
|
||||
}
|
||||
|
||||
// The lower ID has to come first
|
||||
if (account1.to_bn().greaterEquals(account2.to_bn())) {
|
||||
var tmp = account2;
|
||||
account2 = account1;
|
||||
account1 = tmp;
|
||||
}
|
||||
|
||||
var index = new SerializedObject();
|
||||
|
||||
index.append([0, Ledger.space.rippleState.charCodeAt(0)]);
|
||||
index.append(account1.to_bytes());
|
||||
index.append(account2.to_bytes());
|
||||
index.append(currency.to_bytes());
|
||||
|
||||
return index.hash();
|
||||
};
|
||||
|
||||
Ledger.prototype.parse_json = function (v) {
|
||||
this.ledger_json = v;
|
||||
};
|
||||
|
||||
Ledger.prototype.calc_tx_hash = function () {
|
||||
var tx_map = new SHAMap();
|
||||
|
||||
this.ledger_json.transactions.forEach(function (tx_json) {
|
||||
var tx = Transaction.from_json(tx_json);
|
||||
var meta = SerializedObject.from_json(tx_json.metaData);
|
||||
|
||||
var data = new SerializedObject();
|
||||
stypes.VariableLength.serialize(data, tx.serialize().to_hex());
|
||||
stypes.VariableLength.serialize(data, meta.to_hex());
|
||||
tx_map.add_item(tx.hash(), data, SHAMapTreeNode.TYPE_TRANSACTION_MD);
|
||||
});
|
||||
|
||||
return tx_map.hash();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param options.sanity_test {Boolean}
|
||||
*
|
||||
* If `true`, will serialize each accountState item to binary and then back to
|
||||
* json before finally serializing for hashing. This is mostly to expose any
|
||||
* issues with ripple-lib's binary <--> json codecs.
|
||||
*
|
||||
*/
|
||||
Ledger.prototype.calc_account_hash = function (options) {
|
||||
var account_map = new SHAMap();
|
||||
var erred;
|
||||
|
||||
this.ledger_json.accountState.forEach(function (le) {
|
||||
var data = SerializedObject.from_json(le);
|
||||
|
||||
if (options != null && options.sanity_test) {
|
||||
try {
|
||||
var json = data.to_json();
|
||||
data = SerializedObject.from_json(json);
|
||||
} catch (e) {
|
||||
console.log("account state item: ", le);
|
||||
console.log("to_json() ",json);
|
||||
console.log("exception: ", e);
|
||||
erred = true;
|
||||
}
|
||||
};
|
||||
|
||||
account_map.add_item(le.index, data, SHAMapTreeNode.TYPE_ACCOUNT_STATE);
|
||||
});
|
||||
|
||||
if (erred) {
|
||||
throw new Error("There were errors with sanity_test"); // all logged above
|
||||
}
|
||||
|
||||
return account_map.hash();
|
||||
};
|
||||
|
||||
exports.Ledger = Ledger;
|
||||
22
src/js/ripple/ledgerspaces.js
Normal file
22
src/js/ripple/ledgerspaces.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Ripple ledger namespace prefixes.
|
||||
*
|
||||
* The Ripple ledger is a key-value store. In order to avoid name collisions,
|
||||
* names are partitioned into namespaces.
|
||||
*
|
||||
* Each namespace is just a single character prefix.
|
||||
*/
|
||||
module.exports = {
|
||||
account : 'a',
|
||||
dirNode : 'd',
|
||||
generatorMap : 'g',
|
||||
nickname : 'n',
|
||||
rippleState : 'r',
|
||||
offer : 'o', // Entry for an offer.
|
||||
ownerDir : 'O', // Directory of things owned by an account.
|
||||
bookDir : 'B', // Directory of order books.
|
||||
contract : 'c',
|
||||
skipList : 's',
|
||||
amendment : 'f',
|
||||
feeSettings : 'e'
|
||||
};
|
||||
110
src/js/ripple/log.js
Normal file
110
src/js/ripple/log.js
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Logging functionality for ripple-lib and any applications built on it.
|
||||
*/
|
||||
function Log(namespace) {
|
||||
if (!namespace) {
|
||||
this._namespace = [];
|
||||
} else if (Array.isArray(namespace)) {
|
||||
this._namespace = namespace;
|
||||
} else {
|
||||
this._namespace = [''+namespace];
|
||||
}
|
||||
|
||||
this._prefix = this._namespace.concat(['']).join(': ');
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a sub-logger.
|
||||
*
|
||||
* You can have a hierarchy of loggers.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var log = require('ripple').log.sub('server');
|
||||
*
|
||||
* log.info('connection successful');
|
||||
* // prints: 'server: connection successful'
|
||||
*/
|
||||
Log.prototype.sub = function(namespace) {
|
||||
var subNamespace = this._namespace.slice();
|
||||
|
||||
if (namespace && typeof namespace === 'string') {
|
||||
subNamespace.push(namespace);
|
||||
}
|
||||
|
||||
var subLogger = new Log(subNamespace);
|
||||
subLogger._setParent(this);
|
||||
return subLogger;
|
||||
};
|
||||
|
||||
Log.prototype._setParent = function(parentLogger) {
|
||||
this._parent = parentLogger;
|
||||
};
|
||||
|
||||
Log.makeLevel = function(level) {
|
||||
return function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args[0] = this._prefix + args[0];
|
||||
Log.engine.logObject.apply(Log, args);
|
||||
};
|
||||
};
|
||||
|
||||
Log.prototype.debug = Log.makeLevel(1);
|
||||
Log.prototype.info = Log.makeLevel(2);
|
||||
Log.prototype.warn = Log.makeLevel(3);
|
||||
Log.prototype.error = Log.makeLevel(4);
|
||||
|
||||
/**
|
||||
* Basic logging connector.
|
||||
*
|
||||
* This engine has no formatting and works with the most basic of 'console.log'
|
||||
* implementations. This is the logging engine used in Node.js.
|
||||
*/
|
||||
var BasicLogEngine = {
|
||||
logObject: function logObject(msg) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
args = args.map(function(arg) {
|
||||
return JSON.stringify(arg, null, 2);
|
||||
});
|
||||
|
||||
args.unshift(msg);
|
||||
args.unshift('[' + new Date().toISOString() + ']');
|
||||
|
||||
console.log.apply(console, args);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Null logging connector.
|
||||
*
|
||||
* This engine simply swallows all messages. Used when console.log is not
|
||||
* available.
|
||||
*/
|
||||
var NullLogEngine = {
|
||||
logObject: function() {}
|
||||
};
|
||||
|
||||
Log.engine = NullLogEngine;
|
||||
|
||||
if (console && console.log) {
|
||||
Log.engine = BasicLogEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a root logger as our main export.
|
||||
*
|
||||
* This means you can use the logger easily on the fly:
|
||||
* ripple.log.debug('My object is', myObj);
|
||||
*/
|
||||
module.exports = new Log();
|
||||
|
||||
/**
|
||||
* This is the logger for ripple-lib internally.
|
||||
*/
|
||||
module.exports.internal = module.exports.sub();
|
||||
|
||||
/**
|
||||
* Expose the class as well.
|
||||
*/
|
||||
module.exports.Log = Log;
|
||||
31
src/js/ripple/log.web.js
Normal file
31
src/js/ripple/log.web.js
Normal file
@@ -0,0 +1,31 @@
|
||||
var exports = module.exports = require('./log.js');
|
||||
|
||||
/**
|
||||
* Log engine for browser consoles.
|
||||
*
|
||||
* Browsers tend to have better consoles that support nicely formatted
|
||||
* JavaScript objects. This connector passes objects through to the logging
|
||||
* function without any stringification.
|
||||
*/
|
||||
var InteractiveLogEngine = {
|
||||
logObject: function (msg, obj) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
args = args.map(function(arg) {
|
||||
if (/MSIE/.test(navigator.userAgent)) {
|
||||
return JSON.stringify(arg, null, 2);
|
||||
} else {
|
||||
return arg;
|
||||
}
|
||||
});
|
||||
|
||||
args.unshift(msg);
|
||||
args.unshift('[' + new Date().toISOString() + ']');
|
||||
|
||||
console.log.apply(console, args);
|
||||
}
|
||||
};
|
||||
|
||||
if (window.console && window.console.log) {
|
||||
exports.Log.engine = InteractiveLogEngine;
|
||||
}
|
||||
203
src/js/ripple/message.js
Normal file
203
src/js/ripple/message.js
Normal file
@@ -0,0 +1,203 @@
|
||||
var async = require('async');
|
||||
var crypto = require('crypto');
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var Remote = require('./remote').Remote;
|
||||
var Seed = require('./seed').Seed;
|
||||
var KeyPair = require('./keypair').KeyPair;
|
||||
var Account = require('./account').Account;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
|
||||
// Message class (static)
|
||||
var Message = {};
|
||||
|
||||
Message.HASH_FUNCTION = sjcl.hash.sha512.hash;
|
||||
Message.MAGIC_BYTES = 'Ripple Signed Message:\n';
|
||||
|
||||
var REGEX_HEX = /^[0-9a-fA-F]+$/;
|
||||
var REGEX_BASE64 = /^([A-Za-z0-9\+]{4})*([A-Za-z0-9\+]{2}==)|([A-Za-z0-9\+]{3}=)?$/;
|
||||
|
||||
/**
|
||||
* Produce a Base64-encoded signature on the given message with
|
||||
* the string 'Ripple Signed Message:\n' prepended.
|
||||
*
|
||||
* Note that this signature uses the signing function that includes
|
||||
* a recovery_factor to be able to extract the public key from the signature
|
||||
* without having to pass the public key along with the signature.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {String} message
|
||||
* @param {sjcl.ecc.ecdsa.secretKey|Any format accepted by Seed.from_json} secret_key
|
||||
* @param {RippleAddress} [The first key] account Field to specify the signing account.
|
||||
* If this is omitted the first account produced by the secret generator will be used.
|
||||
* @returns {Base64-encoded String} signature
|
||||
*/
|
||||
Message.signMessage = function(message, secret_key, account) {
|
||||
|
||||
return Message.signHash(Message.HASH_FUNCTION(Message.MAGIC_BYTES + message), secret_key, account);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Produce a Base64-encoded signature on the given hex-encoded hash.
|
||||
*
|
||||
* Note that this signature uses the signing function that includes
|
||||
* a recovery_factor to be able to extract the public key from the signature
|
||||
* without having to pass the public key along with the signature.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {bitArray|Hex-encoded String} hash
|
||||
* @param {sjcl.ecc.ecdsa.secretKey|Any format accepted by Seed.from_json} secret_key
|
||||
* @param {RippleAddress} [The first key] account Field to specify the signing account.
|
||||
* If this is omitted the first account produced by the secret generator will be used.
|
||||
* @returns {Base64-encoded String} signature
|
||||
*/
|
||||
Message.signHash = function(hash, secret_key, account) {
|
||||
|
||||
if (typeof hash === 'string' && /^[0-9a-fA-F]+$/.test(hash)) {
|
||||
hash = sjcl.codec.hex.toBits(hash);
|
||||
}
|
||||
|
||||
if (typeof hash !== 'object' || hash.length <= 0 || typeof hash[0] !== 'number') {
|
||||
throw new Error('Hash must be a bitArray or hex-encoded string');
|
||||
}
|
||||
|
||||
if (!(secret_key instanceof sjcl.ecc.ecdsa.secretKey)) {
|
||||
secret_key = Seed.from_json(secret_key).get_key(account)._secret;
|
||||
}
|
||||
|
||||
var signature_bits = secret_key.signWithRecoverablePublicKey(hash);
|
||||
var signature_base64 = sjcl.codec.base64.fromBits(signature_bits);
|
||||
|
||||
return signature_base64;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Verify the signature on a given message.
|
||||
*
|
||||
* Note that this function is asynchronous.
|
||||
* The ripple-lib remote is used to check that the public
|
||||
* key extracted from the signature corresponds to one that is currently
|
||||
* active for the given account.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {String} data.message
|
||||
* @param {RippleAddress} data.account
|
||||
* @param {Base64-encoded String} data.signature
|
||||
* @param {ripple-lib Remote} remote
|
||||
* @param {Function} callback
|
||||
*
|
||||
* @callback callback
|
||||
* @param {Error} error
|
||||
* @param {boolean} is_valid true if the signature is valid, false otherwise
|
||||
*/
|
||||
Message.verifyMessageSignature = function(data, remote, callback) {
|
||||
|
||||
if (typeof data.message === 'string') {
|
||||
data.hash = Message.HASH_FUNCTION(Message.MAGIC_BYTES + data.message);
|
||||
} else {
|
||||
return callback(new Error('Data object must contain message field to verify signature'));
|
||||
}
|
||||
|
||||
return Message.verifyHashSignature(data, remote, callback);
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Verify the signature on a given hash.
|
||||
*
|
||||
* Note that this function is asynchronous.
|
||||
* The ripple-lib remote is used to check that the public
|
||||
* key extracted from the signature corresponds to one that is currently
|
||||
* active for the given account.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {bitArray|Hex-encoded String} data.hash
|
||||
* @param {RippleAddress} data.account
|
||||
* @param {Base64-encoded String} data.signature
|
||||
* @param {ripple-lib Remote} remote
|
||||
* @param {Function} callback
|
||||
*
|
||||
* @callback callback
|
||||
* @param {Error} error
|
||||
* @param {boolean} is_valid true if the signature is valid, false otherwise
|
||||
*/
|
||||
Message.verifyHashSignature = function(data, remote, callback) {
|
||||
|
||||
var hash,
|
||||
account,
|
||||
signature;
|
||||
|
||||
if(typeof callback !== 'function') {
|
||||
throw new Error('Must supply callback function');
|
||||
}
|
||||
|
||||
hash = data.hash;
|
||||
if (hash && typeof hash === 'string' && REGEX_HEX.test(hash)) {
|
||||
hash = sjcl.codec.hex.toBits(hash);
|
||||
}
|
||||
|
||||
if (typeof hash !== 'object' || hash.length <= 0 || typeof hash[0] !== 'number') {
|
||||
return callback(new Error('Hash must be a bitArray or hex-encoded string'));
|
||||
}
|
||||
|
||||
account = data.account || data.address;
|
||||
if (!account || !UInt160.from_json(account).is_valid()) {
|
||||
return callback(new Error('Account must be a valid ripple address'));
|
||||
}
|
||||
|
||||
signature = data.signature;
|
||||
if (typeof signature !== 'string' || !REGEX_BASE64.test(signature)) {
|
||||
return callback(new Error('Signature must be a Base64-encoded string'));
|
||||
}
|
||||
signature = sjcl.codec.base64.toBits(signature);
|
||||
|
||||
if (!(remote instanceof Remote) || remote.state !== 'online') {
|
||||
return callback(new Error('Must supply connected Remote to verify signature'));
|
||||
}
|
||||
|
||||
function recoverPublicKey (async_callback) {
|
||||
|
||||
var public_key;
|
||||
try {
|
||||
public_key = sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature);
|
||||
} catch (err) {
|
||||
return async_callback(err);
|
||||
}
|
||||
|
||||
if (public_key) {
|
||||
async_callback(null, public_key);
|
||||
} else {
|
||||
async_callback(new Error('Could not recover public key from signature'));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function checkPublicKeyIsValid (public_key, async_callback) {
|
||||
|
||||
// Get hex-encoded public key
|
||||
var key_pair = new KeyPair();
|
||||
key_pair._pubkey = public_key;
|
||||
var public_key_hex = key_pair.to_hex_pub();
|
||||
|
||||
var account_class_instance = new Account(remote, account);
|
||||
account_class_instance.publicKeyIsActive(public_key_hex, async_callback);
|
||||
|
||||
};
|
||||
|
||||
var steps = [
|
||||
recoverPublicKey,
|
||||
checkPublicKeyIsValid
|
||||
];
|
||||
|
||||
async.waterfall(steps, callback);
|
||||
|
||||
};
|
||||
|
||||
exports.Message = Message;
|
||||
@@ -1,39 +1,119 @@
|
||||
var extend = require('extend');
|
||||
var utils = require('./utils');
|
||||
var extend = require('extend');
|
||||
var utils = require('./utils');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Amount = require('./amount').Amount;
|
||||
var Amount = require('./amount').Amount;
|
||||
|
||||
/**
|
||||
* Meta data processing facility.
|
||||
* Meta data processing facility
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} transaction metadata
|
||||
*/
|
||||
var Meta = function (raw_data)
|
||||
{
|
||||
this.nodes = [];
|
||||
|
||||
for (var i = 0, l = raw_data.AffectedNodes.length; i < l; i++) {
|
||||
var an = raw_data.AffectedNodes[i],
|
||||
result = {};
|
||||
function Meta(data) {
|
||||
var self = this;
|
||||
|
||||
["CreatedNode", "ModifiedNode", "DeletedNode"].forEach(function (x) {
|
||||
if (an[x]) result.diffType = x;
|
||||
});
|
||||
this.nodes = [ ];
|
||||
|
||||
if (!result.diffType) return null;
|
||||
if (typeof data !== 'object') {
|
||||
throw new TypeError('Missing metadata');
|
||||
}
|
||||
|
||||
an = an[result.diffType];
|
||||
if (!Array.isArray(data.AffectedNodes)) {
|
||||
throw new TypeError('Metadata missing AffectedNodes');
|
||||
}
|
||||
|
||||
result.entryType = an.LedgerEntryType;
|
||||
result.ledgerIndex = an.LedgerIndex;
|
||||
data.AffectedNodes.forEach(this.addNode, this);
|
||||
};
|
||||
|
||||
result.fields = extend({}, an.PreviousFields, an.NewFields, an.FinalFields);
|
||||
result.fieldsPrev = an.PreviousFields || {};
|
||||
result.fieldsNew = an.NewFields || {};
|
||||
result.fieldsFinal = an.FinalFields || {};
|
||||
Meta.nodeTypes = [
|
||||
'CreatedNode',
|
||||
'ModifiedNode',
|
||||
'DeletedNode'
|
||||
];
|
||||
|
||||
Meta.amountFieldsAffectingIssuer = [
|
||||
'LowLimit',
|
||||
'HighLimit',
|
||||
'TakerPays',
|
||||
'TakerGets'
|
||||
];
|
||||
|
||||
/**
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Meta.prototype.getNodeType = function(node) {
|
||||
var result = null;
|
||||
|
||||
for (var i=0; i<Meta.nodeTypes.length; i++) {
|
||||
var type = Meta.nodeTypes[i];
|
||||
if (node.hasOwnProperty(type)) {
|
||||
result = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add node to metadata
|
||||
*
|
||||
* @param {Object} node
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Meta.prototype.addNode = function(node) {
|
||||
this._affectedAccounts = void(0);
|
||||
this._affectedBooks = void(0);
|
||||
|
||||
var result = { };
|
||||
|
||||
if ((result.nodeType = this.getNodeType(node))) {
|
||||
node = node[result.nodeType];
|
||||
|
||||
result.diffType = result.nodeType;
|
||||
result.entryType = node.LedgerEntryType;
|
||||
result.ledgerIndex = node.LedgerIndex;
|
||||
result.fields = extend({ }, node.PreviousFields, node.NewFields, node.FinalFields);
|
||||
result.fieldsPrev = node.PreviousFields || { };
|
||||
result.fieldsNew = node.NewFields || { };
|
||||
result.fieldsFinal = node.FinalFields || { };
|
||||
|
||||
// getAffectedBooks will set this
|
||||
// result.bookKey = undefined;
|
||||
|
||||
this.nodes.push(result);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get affected nodes array
|
||||
*
|
||||
* @param {Object} filter options
|
||||
* @return {Array} nodes
|
||||
*/
|
||||
|
||||
Meta.prototype.getNodes = function(options) {
|
||||
if (typeof options === 'object') {
|
||||
return this.nodes.filter(function(node) {
|
||||
if (options.nodeType && options.nodeType !== node.nodeType) {
|
||||
return false;
|
||||
}
|
||||
if (options.entryType && options.entryType !== node.entryType) {
|
||||
return false;
|
||||
}
|
||||
if (options.bookKey && options.bookKey !== node.bookKey) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
return this.nodes;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a function on each affected node.
|
||||
*
|
||||
@@ -42,7 +122,7 @@ var Meta = function (raw_data)
|
||||
*
|
||||
* {
|
||||
* // Type of diff, e.g. CreatedNode, ModifiedNode
|
||||
* diffType: 'CreatedNode'
|
||||
* nodeType: 'CreatedNode'
|
||||
*
|
||||
* // Type of node affected, e.g. RippleState, AccountRoot
|
||||
* entryType: 'RippleState',
|
||||
@@ -53,7 +133,7 @@ var Meta = function (raw_data)
|
||||
* // Contains all fields with later versions taking precedence
|
||||
* //
|
||||
* // This is a shorthand for doing things like checking which account
|
||||
* // this affected without having to check the diffType.
|
||||
* // this affected without having to check the nodeType.
|
||||
* fields: {...},
|
||||
*
|
||||
* // Old fields (before the change)
|
||||
@@ -69,31 +149,39 @@ var Meta = function (raw_data)
|
||||
* The second parameter to the callback is the index of the node in the metadata
|
||||
* (first entry is index 0).
|
||||
*/
|
||||
Meta.prototype.each = function (fn)
|
||||
{
|
||||
for (var i = 0, l = this.nodes.length; i < l; i++) {
|
||||
fn(this.nodes[i], i);
|
||||
}
|
||||
};
|
||||
|
||||
var amountFieldsAffectingIssuer = [
|
||||
"LowLimit", "HighLimit", "TakerPays", "TakerGets"
|
||||
];
|
||||
Meta.prototype.getAffectedAccounts = function ()
|
||||
{
|
||||
var accounts = [];
|
||||
[
|
||||
'forEach',
|
||||
'map',
|
||||
'filter',
|
||||
'every',
|
||||
'some',
|
||||
'reduce'
|
||||
].forEach(function(fn) {
|
||||
Meta.prototype[fn] = function() {
|
||||
return Array.prototype[fn].apply(this.nodes, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
Meta.prototype.each = Meta.prototype.forEach;
|
||||
|
||||
Meta.prototype.getAffectedAccounts = function(from) {
|
||||
if (this._affectedAccounts) {
|
||||
return this._affectedAccounts;
|
||||
}
|
||||
|
||||
var accounts = [ ];
|
||||
|
||||
// This code should match the behavior of the C++ method:
|
||||
// TransactionMetaSet::getAffectedAccounts
|
||||
this.each(function (an) {
|
||||
var fields = (an.diffType === "CreatedNode") ? an.fieldsNew : an.fieldsFinal;
|
||||
|
||||
for (var i in fields) {
|
||||
var field = fields[i];
|
||||
|
||||
if ("string" === typeof field && UInt160.is_valid(field)) {
|
||||
for (var i=0; i<this.nodes.length; i++) {
|
||||
var node = this.nodes[i];
|
||||
var fields = (node.nodeType === 'CreatedNode') ? node.fieldsNew : node.fieldsFinal;
|
||||
for (var fieldName in fields) {
|
||||
var field = fields[fieldName];
|
||||
if (typeof field === 'string' && UInt160.is_valid(field)) {
|
||||
accounts.push(field);
|
||||
} else if (amountFieldsAffectingIssuer.indexOf(i) !== -1) {
|
||||
} else if (~Meta.amountFieldsAffectingIssuer.indexOf(fieldName)) {
|
||||
var amount = Amount.from_json(field);
|
||||
var issuer = amount.issuer();
|
||||
if (issuer.is_valid() && !issuer.is_zero()) {
|
||||
@@ -101,37 +189,53 @@ Meta.prototype.getAffectedAccounts = function ()
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
accounts = utils.arrayUnique(accounts);
|
||||
this._affectedAccounts = utils.arrayUnique(accounts);
|
||||
|
||||
return accounts;
|
||||
return this._affectedAccounts;
|
||||
};
|
||||
|
||||
Meta.prototype.getAffectedBooks = function ()
|
||||
{
|
||||
var books = [];
|
||||
Meta.prototype.getAffectedBooks = function() {
|
||||
if (this._affectedBooks) {
|
||||
return this._affectedBooks;
|
||||
}
|
||||
|
||||
this.each(function (an) {
|
||||
if (an.entryType !== 'Offer') return;
|
||||
var books = [ ];
|
||||
|
||||
var gets = Amount.from_json(an.fields.TakerGets);
|
||||
var pays = Amount.from_json(an.fields.TakerPays);
|
||||
for (var i=0; i<this.nodes.length; i++) {
|
||||
var node = this.nodes[i];
|
||||
|
||||
if (node.entryType !== 'Offer') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var gets = Amount.from_json(node.fields.TakerGets);
|
||||
var pays = Amount.from_json(node.fields.TakerPays);
|
||||
var getsKey = gets.currency().to_json();
|
||||
if (getsKey !== 'XRP') getsKey += '/' + gets.issuer().to_json();
|
||||
|
||||
var paysKey = pays.currency().to_json();
|
||||
if (paysKey !== 'XRP') paysKey += '/' + pays.issuer().to_json();
|
||||
|
||||
var key = getsKey + ":" + paysKey;
|
||||
if (getsKey !== 'XRP') {
|
||||
getsKey += '/' + gets.issuer().to_json();
|
||||
}
|
||||
|
||||
if (paysKey !== 'XRP') {
|
||||
paysKey += '/' + pays.issuer().to_json();
|
||||
}
|
||||
|
||||
var key = getsKey + ':' + paysKey;
|
||||
|
||||
// Hell of a lot of work, so we are going to cache this. We can use this
|
||||
// later to good effect in OrderBook.notify to make sure we only process
|
||||
// pertinent offers.
|
||||
node.bookKey = key;
|
||||
|
||||
books.push(key);
|
||||
});
|
||||
}
|
||||
|
||||
books = utils.arrayUnique(books);
|
||||
this._affectedBooks = utils.arrayUnique(books);
|
||||
|
||||
return books;
|
||||
return this._affectedBooks;
|
||||
};
|
||||
|
||||
exports.Meta = Meta;
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
//
|
||||
// Access to the Ripple network via multiple untrusted servers or a single trusted server.
|
||||
//
|
||||
// Overview:
|
||||
// Network configuration.
|
||||
// Can leverage local storage to remember network configuration
|
||||
// Aquires the network
|
||||
// events:
|
||||
// online
|
||||
// offline
|
||||
//
|
||||
|
||||
var remote = require("./remote.js");
|
||||
|
||||
var opts_default = {
|
||||
DEFAULT_VALIDATORS_SITE : "redstem.com",
|
||||
|
||||
ips = {
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// opts : {
|
||||
// cache : undefined || {
|
||||
// get : function () { return cached_value; },
|
||||
// set : function (value) { cached_value = value; },
|
||||
// },
|
||||
//
|
||||
// // Where to get validators.txt if needed.
|
||||
// DEFAULT_VALIDATORS_SITE : _domain_,
|
||||
//
|
||||
// // Validator.txt to use.
|
||||
// validators : _txt_,
|
||||
// }
|
||||
//
|
||||
|
||||
var Network = function (opts) {
|
||||
|
||||
};
|
||||
|
||||
// Set the network configuration.
|
||||
Network.protocol.configure = function () {
|
||||
|
||||
};
|
||||
|
||||
// Target state: connectted
|
||||
Network.protocol.start = function () {
|
||||
|
||||
};
|
||||
|
||||
// Target state: disconnect
|
||||
Network.protocol.stop = function () {
|
||||
|
||||
};
|
||||
|
||||
exports.Network = Network;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,7 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
||||
var Amount = require('./amount').Amount;
|
||||
|
||||
var extend = require('extend');
|
||||
var util = require('util');
|
||||
var Amount = require('./amount').Amount;
|
||||
var extend = require('extend');
|
||||
|
||||
/**
|
||||
* Represents a persistent path finding request.
|
||||
@@ -12,9 +10,7 @@ var extend = require('extend');
|
||||
* find request is triggered it will supercede the existing one, making it emit
|
||||
* the 'end' and 'superceded' events.
|
||||
*/
|
||||
var PathFind = function (remote, src_account, dst_account,
|
||||
dst_amount, src_currencies)
|
||||
{
|
||||
function PathFind(remote, src_account, dst_account, dst_amount, src_currencies) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.remote = remote;
|
||||
@@ -36,8 +32,7 @@ util.inherits(PathFind, EventEmitter);
|
||||
* so you should only have to call it if the path find was closed or superceded
|
||||
* and you wish to restart it.
|
||||
*/
|
||||
PathFind.prototype.create = function ()
|
||||
{
|
||||
PathFind.prototype.create = function () {
|
||||
var self = this;
|
||||
|
||||
var req = this.remote.request_path_find_create(this.src_account,
|
||||
@@ -48,24 +43,25 @@ PathFind.prototype.create = function ()
|
||||
|
||||
function handleInitialPath(err, msg) {
|
||||
if (err) {
|
||||
// XXX Handle error
|
||||
return;
|
||||
self.emit('error', err);
|
||||
} else {
|
||||
self.notify_update(msg);
|
||||
}
|
||||
self.notify_update(msg);
|
||||
}
|
||||
|
||||
// XXX We should add ourselves to prepare_subscribe or a similar mechanism so
|
||||
// that we can resubscribe after a reconnection.
|
||||
|
||||
req.request();
|
||||
};
|
||||
|
||||
PathFind.prototype.close = function ()
|
||||
{
|
||||
PathFind.prototype.close = function () {
|
||||
this.remote.request_path_find_close().request();
|
||||
this.emit('end');
|
||||
this.emit('close');
|
||||
};
|
||||
|
||||
PathFind.prototype.notify_update = function (message)
|
||||
{
|
||||
PathFind.prototype.notify_update = function (message) {
|
||||
var src_account = message.source_account;
|
||||
var dst_account = message.destination_account;
|
||||
var dst_amount = Amount.from_json(message.destination_amount);
|
||||
@@ -79,8 +75,10 @@ PathFind.prototype.notify_update = function (message)
|
||||
}
|
||||
};
|
||||
|
||||
PathFind.prototype.notify_superceded = function ()
|
||||
{
|
||||
PathFind.prototype.notify_superceded = function () {
|
||||
// XXX If we're set to re-subscribe whenever we connect to a new server, then
|
||||
// we should cancel that behavior here. See PathFind#create.
|
||||
|
||||
this.emit('end');
|
||||
this.emit('superceded');
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
437
src/js/ripple/request.js
Normal file
437
src/js/ripple/request.js
Normal file
@@ -0,0 +1,437 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Currency = require('./currency').Currency;
|
||||
var RippleError = require('./rippleerror').RippleError;
|
||||
var Server = require('./server').Server;
|
||||
|
||||
// Request events emitted:
|
||||
// 'success' : Request successful.
|
||||
// 'error' : Request failed.
|
||||
// 'remoteError'
|
||||
// 'remoteUnexpected'
|
||||
// 'remoteDisconnected'
|
||||
|
||||
/**
|
||||
* Request
|
||||
*
|
||||
* @param {Remote} remote
|
||||
* @param {String} command
|
||||
*/
|
||||
|
||||
function Request(remote, command) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.remote = remote;
|
||||
this.requested = false;
|
||||
this.message = {
|
||||
command: command,
|
||||
id: void(0)
|
||||
};
|
||||
};
|
||||
|
||||
util.inherits(Request, EventEmitter);
|
||||
|
||||
Request.prototype.broadcast = function() {
|
||||
var connectedServers = this.remote.getConnectedServers();
|
||||
this.request(connectedServers);
|
||||
return connectedServers.length;
|
||||
};
|
||||
|
||||
// Send the request to a remote.
|
||||
Request.prototype.request = function(servers, callback) {
|
||||
if (this.requested) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (typeof servers === 'function') {
|
||||
callback = servers;
|
||||
}
|
||||
|
||||
this.requested = true;
|
||||
this.on('error', function(){});
|
||||
this.emit('request', this.remote);
|
||||
|
||||
if (Array.isArray(servers)) {
|
||||
servers.forEach(function(server) {
|
||||
this.setServer(server);
|
||||
this.remote.request(this);
|
||||
}, this);
|
||||
} else {
|
||||
this.remote.request(this);
|
||||
}
|
||||
|
||||
this.callback(callback);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.callback = function(callback, successEvent, errorEvent) {
|
||||
var self = this;
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
return this;
|
||||
}
|
||||
|
||||
var called = false;
|
||||
|
||||
function requestSuccess(message) {
|
||||
if (!called) {
|
||||
called = true;
|
||||
callback.call(self, null, message);
|
||||
}
|
||||
};
|
||||
|
||||
function requestError(error) {
|
||||
if (!called) {
|
||||
called = true;
|
||||
|
||||
if (!(error instanceof RippleError)) {
|
||||
error = new RippleError(error);
|
||||
}
|
||||
|
||||
callback.call(self, error);
|
||||
}
|
||||
};
|
||||
|
||||
this.once(successEvent || 'success', requestSuccess);
|
||||
this.once(errorEvent || 'error' , requestError);
|
||||
this.request();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.timeout = function(duration, callback) {
|
||||
var self = this;
|
||||
|
||||
function requested() {
|
||||
self.timeout(duration, callback);
|
||||
};
|
||||
|
||||
if (!this.requested) {
|
||||
// Defer until requested
|
||||
return this.once('request', requested);
|
||||
}
|
||||
|
||||
var emit = this.emit;
|
||||
var timed_out = false;
|
||||
|
||||
var timeout = setTimeout(function() {
|
||||
timed_out = true;
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
|
||||
emit.call(self, 'timeout');
|
||||
}, duration);
|
||||
|
||||
this.emit = function() {
|
||||
if (!timed_out) {
|
||||
clearTimeout(timeout);
|
||||
emit.apply(self, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setServer = function(server) {
|
||||
var selected = null;
|
||||
|
||||
switch (typeof server) {
|
||||
case 'object':
|
||||
selected = server;
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
// Find server by URL
|
||||
var servers = this.remote._servers;
|
||||
|
||||
for (var i=0, s; (s=servers[i]); i++) {
|
||||
if (s._url === server) {
|
||||
selected = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
};
|
||||
|
||||
if (selected instanceof Server) {
|
||||
this.server = selected;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.buildPath = function(build) {
|
||||
if (this.remote.local_signing) {
|
||||
throw new Error(
|
||||
'`build_path` is completely ignored when doing local signing as '
|
||||
+ '`Paths` is a component of the signed blob. The `tx_blob` is signed,'
|
||||
+ 'sealed and delivered, and the txn unmodified after' );
|
||||
}
|
||||
|
||||
if (build) {
|
||||
this.message.build_path = true;
|
||||
} else {
|
||||
// ND: rippled currently intreprets the mere presence of `build_path` as the
|
||||
// value being `truthy`
|
||||
delete this.message.build_path;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.ledgerChoose = function(current) {
|
||||
if (current) {
|
||||
this.message.ledger_index = this.remote._ledger_current_index;
|
||||
} else {
|
||||
this.message.ledger_hash = this.remote._ledger_hash;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Set the ledger for a request.
|
||||
// - ledger_entry
|
||||
// - transaction_entry
|
||||
Request.prototype.ledgerHash = function(hash) {
|
||||
this.message.ledger_hash = hash;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Set the ledger_index for a request.
|
||||
// - ledger_entry
|
||||
Request.prototype.ledgerIndex = function(ledger_index) {
|
||||
this.message.ledger_index = ledger_index;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set either ledger_index or ledger_hash based on heuristic
|
||||
*
|
||||
* @param {Number|String} ledger identifier
|
||||
*/
|
||||
|
||||
Request.prototype.selectLedger =
|
||||
Request.prototype.ledgerSelect = function(ledger) {
|
||||
switch (ledger) {
|
||||
case 'current':
|
||||
case 'closed':
|
||||
case 'validated':
|
||||
this.message.ledger_index = ledger;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (Number(ledger)) {
|
||||
this.message.ledger_index = Number(ledger);
|
||||
} else if (/^[A-F0-9]+$/.test(ledger)) {
|
||||
this.message.ledger_hash = ledger;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.accountRoot = function(account) {
|
||||
this.message.account_root = UInt160.json_rewrite(account);
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.index = function(index) {
|
||||
this.message.index = index;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Provide the information id an offer.
|
||||
// --> account
|
||||
// --> seq : sequence number of transaction creating offer (integer)
|
||||
Request.prototype.offerId = function(account, sequence) {
|
||||
this.message.offer = {
|
||||
account: UInt160.json_rewrite(account),
|
||||
seq: sequence
|
||||
};
|
||||
return this;
|
||||
};
|
||||
|
||||
// --> index : ledger entry index.
|
||||
Request.prototype.offerIndex = function(index) {
|
||||
this.message.offer = index;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.secret = function(secret) {
|
||||
if (secret) {
|
||||
this.message.secret = secret;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.txHash = function(hash) {
|
||||
this.message.tx_hash = hash;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.txJson = function(json) {
|
||||
this.message.tx_json = json;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.txBlob = function(json) {
|
||||
this.message.tx_blob = json;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.rippleState = function(account, issuer, currency) {
|
||||
this.message.ripple_state = {
|
||||
currency : currency,
|
||||
accounts : [
|
||||
UInt160.json_rewrite(account),
|
||||
UInt160.json_rewrite(issuer)
|
||||
]
|
||||
};
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setAccounts =
|
||||
Request.prototype.accounts = function(accounts, proposed) {
|
||||
if (!Array.isArray(accounts)) {
|
||||
accounts = [ accounts ];
|
||||
}
|
||||
|
||||
// Process accounts parameters
|
||||
var processedAccounts = accounts.map(function(account) {
|
||||
return UInt160.json_rewrite(account);
|
||||
});
|
||||
|
||||
if (proposed) {
|
||||
this.message.accounts_proposed = processedAccounts;
|
||||
} else {
|
||||
this.message.accounts = processedAccounts;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.addAccount = function(account, proposed) {
|
||||
if (Array.isArray(account)) {
|
||||
account.forEach(this.addAccount, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
var processedAccount = UInt160.json_rewrite(account);
|
||||
|
||||
if (proposed === true) {
|
||||
this.message.accounts_proposed = (this.message.accounts_proposed || []).concat(processedAccount);
|
||||
} else {
|
||||
this.message.accounts = (this.message.accounts || []).concat(processedAccount);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setAccountsProposed =
|
||||
Request.prototype.rtAccounts =
|
||||
Request.prototype.accountsProposed = function(accounts) {
|
||||
return this.accounts(accounts, true);
|
||||
};
|
||||
|
||||
Request.prototype.addAccountProposed = function(account) {
|
||||
if (Array.isArray(account)) {
|
||||
account.forEach(this.addAccountProposed, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
return this.addAccount(account, true);
|
||||
};
|
||||
|
||||
Request.prototype.setBooks =
|
||||
Request.prototype.books = function(books, snapshot) {
|
||||
// Reset list of books (this method overwrites the current list)
|
||||
this.message.books = [ ];
|
||||
|
||||
for (var i=0, l=books.length; i<l; i++) {
|
||||
var book = books[i];
|
||||
this.addBook(book, snapshot);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.addBook = function(book, snapshot) {
|
||||
if (Array.isArray(book)) {
|
||||
book.forEach(this.addBook, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
var json = { };
|
||||
|
||||
function processSide(side) {
|
||||
if (!book[side]) {
|
||||
throw new Error('Missing ' + side);
|
||||
}
|
||||
|
||||
var obj = json[side] = {
|
||||
currency: Currency.json_rewrite(book[side].currency, { force_hex: true })
|
||||
};
|
||||
|
||||
if (!Currency.from_json(obj.currency).is_native()) {
|
||||
obj.issuer = UInt160.json_rewrite(book[side].issuer);
|
||||
}
|
||||
}
|
||||
|
||||
[ 'taker_gets', 'taker_pays' ].forEach(processSide);
|
||||
|
||||
if (typeof snapshot !== 'boolean') {
|
||||
json.snapshot = true;
|
||||
} else if (snapshot) {
|
||||
json.snapshot = true;
|
||||
} else {
|
||||
delete json.snapshot;
|
||||
}
|
||||
|
||||
if (book.both) {
|
||||
json.both = true;
|
||||
}
|
||||
|
||||
this.message.books = (this.message.books || []).concat(json);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.addStream = function(stream, values) {
|
||||
var self = this;
|
||||
|
||||
if (Array.isArray(values)) {
|
||||
switch (stream) {
|
||||
case 'accounts':
|
||||
this.addAccount(values);
|
||||
break;
|
||||
case 'accounts_proposed':
|
||||
this.addAccountProposed(values);
|
||||
break;
|
||||
case 'books':
|
||||
this.addBook(values);
|
||||
break;
|
||||
}
|
||||
} else if (arguments.length > 1) {
|
||||
for (arg in arguments) {
|
||||
this.addStream(arguments[arg]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(this.message.streams)) {
|
||||
this.message.streams = [ ];
|
||||
}
|
||||
|
||||
if (this.message.streams.indexOf(stream) === -1) {
|
||||
this.message.streams.push(stream);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
exports.Request = Request;
|
||||
33
src/js/ripple/rippleerror.js
Normal file
33
src/js/ripple/rippleerror.js
Normal file
@@ -0,0 +1,33 @@
|
||||
var util = require('util');
|
||||
var extend = require('extend');
|
||||
|
||||
function RippleError(code, message) {
|
||||
switch (typeof code) {
|
||||
case 'object':
|
||||
extend(this, code);
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
this.result = code;
|
||||
this.result_message = message;
|
||||
break;
|
||||
}
|
||||
|
||||
this.engine_result = this.result = (this.result || this.engine_result || this.error || 'Error');
|
||||
this.engine_result_message = this.result_message = (this.result_message || this.engine_result_message || this.error_message || 'Error');
|
||||
this.result_message = this.message = (this.result_message);
|
||||
|
||||
var stack;
|
||||
|
||||
if (!!Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, code || this);
|
||||
} else if ((stack = new Error().stack)) {
|
||||
this.stack = stack;
|
||||
}
|
||||
};
|
||||
|
||||
util.inherits(RippleError, Error);
|
||||
|
||||
RippleError.prototype.name = 'RippleError';
|
||||
|
||||
exports.RippleError = RippleError;
|
||||
133
src/js/ripple/rippletxt.js
Normal file
133
src/js/ripple/rippletxt.js
Normal file
@@ -0,0 +1,133 @@
|
||||
var request = require('superagent');
|
||||
var Currency = require('./currency').Currency;
|
||||
|
||||
var RippleTxt = {
|
||||
txts : { }
|
||||
};
|
||||
|
||||
RippleTxt.urlTemplates = [
|
||||
'https://{{domain}}/ripple.txt',
|
||||
'https://www.{{domain}}/ripple.txt',
|
||||
'https://ripple.{{domain}}/ripple.txt',
|
||||
'http://{{domain}}/ripple.txt',
|
||||
'http://www.{{domain}}/ripple.txt',
|
||||
'http://ripple.{{domain}}/ripple.txt'
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets the ripple.txt file for the given domain
|
||||
* @param {string} domain - Domain to retrieve file from
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
|
||||
RippleTxt.get = function(domain, fn) {
|
||||
var self = this;
|
||||
|
||||
if (self.txts[domain]) {
|
||||
return fn(null, self.txts[domain]);
|
||||
}
|
||||
|
||||
;(function nextUrl(i) {
|
||||
var url = RippleTxt.urlTemplates[i];
|
||||
|
||||
if (!url) {
|
||||
return fn(new Error('No ripple.txt found'));
|
||||
}
|
||||
|
||||
url = url.replace('{{domain}}', domain);
|
||||
|
||||
request.get(url, function(err, resp) {
|
||||
if (err || !resp.text) {
|
||||
return nextUrl(++i);
|
||||
}
|
||||
|
||||
var sections = self.parse(resp.text);
|
||||
self.txts[domain] = sections;
|
||||
|
||||
fn(null, sections);
|
||||
});
|
||||
})(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a ripple.txt file
|
||||
* @param {string} txt - Unparsed ripple.txt data
|
||||
*/
|
||||
|
||||
RippleTxt.parse = function(txt) {
|
||||
var currentSection = '';
|
||||
var sections = { };
|
||||
|
||||
txt = txt.replace(/\r?\n/g, '\n').split('\n');
|
||||
|
||||
for (var i = 0, l = txt.length; i < l; i++) {
|
||||
var line = txt[i];
|
||||
|
||||
if (!line.length || line[0] === '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[0] === '[' && line[line.length - 1] === ']') {
|
||||
currentSection = line.slice(1, line.length - 1);
|
||||
sections[currentSection] = [];
|
||||
} else {
|
||||
line = line.replace(/^\s+|\s+$/g, '');
|
||||
if (sections[currentSection]) {
|
||||
sections[currentSection].push(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sections;
|
||||
};
|
||||
|
||||
/**
|
||||
* extractDomain
|
||||
* attempt to extract the domain from a given url
|
||||
* returns the url if unsuccessful
|
||||
* @param {Object} url
|
||||
*/
|
||||
|
||||
RippleTxt.extractDomain = function (url) {
|
||||
match = /[^.]*\.[^.]{2,3}(?:\.[^.]{2,3})?([^.\?][^\?.]+?)?$/.exec(url);
|
||||
return match && match[0] ? match[0] : url;
|
||||
};
|
||||
|
||||
/**
|
||||
* getCurrencies
|
||||
* returns domain, issuer account and currency object
|
||||
* for each currency found in the domain's ripple.txt file
|
||||
* @param {Object} domain
|
||||
* @param {Object} fn
|
||||
*/
|
||||
|
||||
RippleTxt.getCurrencies = function(domain, fn) {
|
||||
domain = RippleTxt.extractDomain(domain);
|
||||
this.get(domain, function(err, txt) {
|
||||
if (err) {
|
||||
return fn(err);
|
||||
}
|
||||
|
||||
if (err || !txt.currencies || !txt.accounts) {
|
||||
return fn(null, []);
|
||||
}
|
||||
|
||||
//NOTE: this won't be accurate if there are
|
||||
//multiple issuer accounts with different
|
||||
//currencies associated with each.
|
||||
var issuer = txt.accounts[0];
|
||||
var currencies = [];
|
||||
|
||||
txt.currencies.forEach(function(currency) {
|
||||
currencies.push({
|
||||
issuer : issuer,
|
||||
currency : Currency.from_json(currency),
|
||||
domain : domain
|
||||
});
|
||||
});
|
||||
|
||||
fn(null, currencies);
|
||||
});
|
||||
};
|
||||
|
||||
exports.RippleTxt = RippleTxt;
|
||||
@@ -2,21 +2,21 @@
|
||||
// Seed support
|
||||
//
|
||||
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var utils = require('./utils');
|
||||
var jsbn = require('./jsbn');
|
||||
var extend = require('extend');
|
||||
var extend = require('extend');
|
||||
var utils = require('./utils');
|
||||
var sjcl = utils.sjcl;
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
var Base = require('./base').Base,
|
||||
UInt = require('./uint').UInt,
|
||||
UInt256 = require('./uint256').UInt256,
|
||||
KeyPair = require('./keypair').KeyPair;
|
||||
var Base = require('./base').Base;
|
||||
var UInt = require('./uint').UInt;
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var KeyPair = require('./keypair').KeyPair;
|
||||
|
||||
var Seed = extend(function () {
|
||||
// Internal form: NaN or BigInteger
|
||||
this._curve = sjcl.ecc.curves['c256'];
|
||||
this._curve = sjcl.ecc.curves.c256;
|
||||
this._value = NaN;
|
||||
}, UInt);
|
||||
|
||||
@@ -27,14 +27,14 @@ Seed.prototype.constructor = Seed;
|
||||
// value = NaN on error.
|
||||
// One day this will support rfc1751 too.
|
||||
Seed.prototype.parse_json = function (j) {
|
||||
if ('string' === typeof j) {
|
||||
if (typeof j === 'string') {
|
||||
if (!j.length) {
|
||||
this._value = NaN;
|
||||
// XXX Should actually always try and continue if it failed.
|
||||
} else if (j[0] === "s") {
|
||||
} else if (j[0] === 's') {
|
||||
this._value = Base.decode_check(Base.VER_FAMILY_SEED, j);
|
||||
} else if (j.length === 32) {
|
||||
this._value = this.parse_hex(j);
|
||||
} else if (/^[0-9a-fA-f]{32}$/.test(j)) {
|
||||
this.parse_hex(j);
|
||||
// XXX Should also try 1751
|
||||
} else {
|
||||
this.parse_passphrase(j);
|
||||
@@ -47,8 +47,8 @@ Seed.prototype.parse_json = function (j) {
|
||||
};
|
||||
|
||||
Seed.prototype.parse_passphrase = function (j) {
|
||||
if ("string" !== typeof j) {
|
||||
throw new Error("Passphrase must be a string");
|
||||
if (typeof j !== 'string') {
|
||||
throw new Error('Passphrase must be a string');
|
||||
}
|
||||
|
||||
var hash = sjcl.hash.sha512.hash(sjcl.codec.utf8String.toBits(j));
|
||||
@@ -60,8 +60,9 @@ Seed.prototype.parse_passphrase = function (j) {
|
||||
};
|
||||
|
||||
Seed.prototype.to_json = function () {
|
||||
if (!(this._value instanceof BigInteger))
|
||||
if (!(this._value instanceof BigInteger)) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
var output = Base.encode_check(Base.VER_FAMILY_SEED, this.to_bytes());
|
||||
|
||||
@@ -70,30 +71,53 @@ Seed.prototype.to_json = function () {
|
||||
|
||||
function append_int(a, i) {
|
||||
return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff);
|
||||
}
|
||||
};
|
||||
|
||||
function firstHalfOfSHA512(bytes) {
|
||||
return sjcl.bitArray.bitSlice(
|
||||
sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)),
|
||||
0, 256
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function SHA256_RIPEMD160(bits) {
|
||||
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param account
|
||||
* {undefined} take first, default, KeyPair
|
||||
*
|
||||
* {Number} specifies the account number of the KeyPair
|
||||
* desired.
|
||||
*
|
||||
* {Uint160} (from_json able), specifies the address matching the KeyPair
|
||||
* that is desired.
|
||||
*
|
||||
* @param maxLoops (optional)
|
||||
* {Number} specifies the amount of attempts taken to generate
|
||||
* a matching KeyPair
|
||||
*/
|
||||
Seed.prototype.get_key = function (account, maxLoops) {
|
||||
var account_number = 0, address;
|
||||
var max_loops = maxLoops || 1;
|
||||
|
||||
Seed.prototype.get_key = function (account_id) {
|
||||
if (!this.is_valid()) {
|
||||
throw new Error("Cannot generate keys from invalid seed!");
|
||||
throw new Error('Cannot generate keys from invalid seed!');
|
||||
}
|
||||
if (account) {
|
||||
if (typeof account === 'number') {
|
||||
account_number = account;
|
||||
max_loops = account_number+1;
|
||||
} else {
|
||||
address = UInt160.from_json(account);
|
||||
}
|
||||
}
|
||||
// XXX Should loop over keys until we find the right one
|
||||
|
||||
var private_gen, public_gen;
|
||||
var curve = this._curve;
|
||||
var i = 0;
|
||||
|
||||
var seq = 0;
|
||||
|
||||
var private_gen, public_gen, i = 0;
|
||||
do {
|
||||
private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.to_bytes(), i)));
|
||||
i++;
|
||||
@@ -102,15 +126,30 @@ Seed.prototype.get_key = function (account_id) {
|
||||
public_gen = curve.G.mult(private_gen);
|
||||
|
||||
var sec;
|
||||
i = 0;
|
||||
var key_pair;
|
||||
|
||||
do {
|
||||
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i)));
|
||||
i++;
|
||||
} while (!curve.r.greaterEquals(sec));
|
||||
|
||||
sec = sec.add(private_gen).mod(curve.r);
|
||||
i = 0;
|
||||
|
||||
return KeyPair.from_bn_secret(sec);
|
||||
do {
|
||||
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), account_number), i)));
|
||||
i++;
|
||||
} while (!curve.r.greaterEquals(sec));
|
||||
|
||||
account_number++;
|
||||
sec = sec.add(private_gen).mod(curve.r);
|
||||
key_pair = KeyPair.from_bn_secret(sec);
|
||||
|
||||
if (max_loops-- <= 0) {
|
||||
// We are almost certainly looking for an account that would take same
|
||||
// value of $too_long {forever, ...}
|
||||
throw new Error('Too many loops looking for KeyPair yielding '+
|
||||
address.to_json() +' from ' + this.to_json());
|
||||
}
|
||||
|
||||
} while (address && !key_pair.get_address().equals(address));
|
||||
return key_pair;
|
||||
};
|
||||
|
||||
exports.Seed = Seed;
|
||||
|
||||
@@ -1,169 +1,330 @@
|
||||
var binformat = require('./binformat'),
|
||||
sjcl = require('../../../build/sjcl'),
|
||||
extend = require('extend'),
|
||||
stypes = require('./serializedtypes');
|
||||
var assert = require('assert');
|
||||
var extend = require('extend');
|
||||
var binformat = require('./binformat');
|
||||
var stypes = require('./serializedtypes');
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var Crypt = require('./crypt').Crypt;
|
||||
var utils = require('./utils');
|
||||
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var sjcl = utils.sjcl;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
var SerializedObject = function (buf) {
|
||||
if (Array.isArray(buf)) {
|
||||
var TRANSACTION_TYPES = { };
|
||||
|
||||
Object.keys(binformat.tx).forEach(function(key) {
|
||||
TRANSACTION_TYPES[binformat.tx[key][0]] = key;
|
||||
});
|
||||
|
||||
var LEDGER_ENTRY_TYPES = {};
|
||||
|
||||
Object.keys(binformat.ledger).forEach(function(key) {
|
||||
LEDGER_ENTRY_TYPES[binformat.ledger[key][0]] = key;
|
||||
});
|
||||
|
||||
var TRANSACTION_RESULTS = {};
|
||||
|
||||
Object.keys(binformat.ter).forEach(function(key) {
|
||||
TRANSACTION_RESULTS[binformat.ter[key]] = key;
|
||||
});
|
||||
|
||||
function SerializedObject(buf) {
|
||||
if (Array.isArray(buf) || (Buffer && Buffer.isBuffer(buf)) ) {
|
||||
this.buffer = buf;
|
||||
} else if ("string" === typeof buf) {
|
||||
} else if (typeof buf === 'string') {
|
||||
this.buffer = sjcl.codec.bytes.fromBits(sjcl.codec.hex.toBits(buf));
|
||||
} else if (!buf) {
|
||||
this.buffer = [];
|
||||
} else {
|
||||
throw new Error("Invalid buffer passed.");
|
||||
throw new Error('Invalid buffer passed.');
|
||||
}
|
||||
this.pointer = 0;
|
||||
};
|
||||
|
||||
SerializedObject.from_json = function (obj) {
|
||||
var typedef;
|
||||
var so = new SerializedObject();
|
||||
|
||||
SerializedObject.from_json = function(obj) {
|
||||
// Create a copy of the object so we don't modify it
|
||||
obj = extend({}, obj);
|
||||
var obj = extend({}, obj);
|
||||
var so = new SerializedObject();
|
||||
var typedef;
|
||||
|
||||
if ("number" === typeof obj.TransactionType) {
|
||||
if (typeof obj.TransactionType === 'number') {
|
||||
obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType);
|
||||
|
||||
if (!obj.TransactionType) {
|
||||
throw new Error("Transaction type ID is invalid.");
|
||||
throw new Error('Transaction type ID is invalid.');
|
||||
}
|
||||
}
|
||||
|
||||
if ("string" === typeof obj.TransactionType) {
|
||||
typedef = binformat.tx[obj.TransactionType].slice();
|
||||
if (typeof obj.LedgerEntryType === 'number') {
|
||||
obj.LedgerEntryType = SerializedObject.lookup_type_le(obj.LedgerEntryType);
|
||||
|
||||
if (!obj.LedgerEntryType) {
|
||||
throw new Error('LedgerEntryType ID is invalid.');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof obj.TransactionType === 'string') {
|
||||
typedef = binformat.tx[obj.TransactionType];
|
||||
if (!Array.isArray(typedef)) {
|
||||
throw new Error('Transaction type is invalid');
|
||||
}
|
||||
|
||||
typedef = typedef.slice();
|
||||
obj.TransactionType = typedef.shift();
|
||||
} else if ("undefined" !== typeof obj.LedgerEntryType) {
|
||||
// XXX: TODO
|
||||
throw new Error("Ledger entry binary format not yet implemented.");
|
||||
} else throw new Error("Object to be serialized must contain either " +
|
||||
"TransactionType or LedgerEntryType.");
|
||||
} else if (typeof obj.LedgerEntryType === 'string') {
|
||||
typedef = binformat.ledger[obj.LedgerEntryType];
|
||||
|
||||
if (!Array.isArray(typedef)) {
|
||||
throw new Error('LedgerEntryType is invalid');
|
||||
}
|
||||
|
||||
typedef = typedef.slice();
|
||||
obj.LedgerEntryType = typedef.shift();
|
||||
|
||||
} else if (typeof obj.AffectedNodes === 'object') {
|
||||
typedef = binformat.metadata;
|
||||
} else {
|
||||
throw new Error('Object to be serialized must contain either' +
|
||||
' TransactionType, LedgerEntryType or AffectedNodes.');
|
||||
}
|
||||
|
||||
// ND: This from_*json* seems a reasonable place to put validation of `json`
|
||||
SerializedObject.check_no_missing_fields(typedef, obj);
|
||||
so.serialize(typedef, obj);
|
||||
|
||||
return so;
|
||||
};
|
||||
|
||||
SerializedObject.prototype.append = function (bytes) {
|
||||
SerializedObject.check_no_missing_fields = function(typedef, obj) {
|
||||
var missing_fields = [];
|
||||
|
||||
for (var i = typedef.length - 1; i >= 0; i--) {
|
||||
var spec = typedef[i];
|
||||
var field = spec[0];
|
||||
var requirement = spec[1];
|
||||
|
||||
if (binformat.REQUIRED === requirement && obj[field] === void(0)) {
|
||||
missing_fields.push(field);
|
||||
};
|
||||
};
|
||||
|
||||
if (missing_fields.length > 0) {
|
||||
var object_name;
|
||||
|
||||
if (obj.TransactionType !== void(0)) {
|
||||
object_name = SerializedObject.lookup_type_tx(obj.TransactionType);
|
||||
} else if (obj.LedgerEntryType != null){
|
||||
object_name = SerializedObject.lookup_type_le(obj.LedgerEntryType);
|
||||
} else {
|
||||
object_name = "TransactionMetaData";
|
||||
}
|
||||
|
||||
throw new Error(object_name + " is missing fields: " +
|
||||
JSON.stringify(missing_fields));
|
||||
};
|
||||
};
|
||||
|
||||
SerializedObject.prototype.append = function(bytes) {
|
||||
if (bytes instanceof SerializedObject) {
|
||||
bytes = bytes.buffer;
|
||||
}
|
||||
|
||||
this.buffer = this.buffer.concat(bytes);
|
||||
this.pointer += bytes.length;
|
||||
};
|
||||
|
||||
SerializedObject.prototype.resetPointer = function () {
|
||||
SerializedObject.prototype.resetPointer = function() {
|
||||
this.pointer = 0;
|
||||
};
|
||||
|
||||
SerializedObject.prototype.read = function (numberOfBytes) {
|
||||
var start = this.pointer;
|
||||
var end = start+numberOfBytes;
|
||||
if (end > this.buffer.length) {
|
||||
throw new Error("There aren't that many bytes left to read.");
|
||||
} else {
|
||||
var result = this.buffer.slice(start,end);
|
||||
this.pointer = end;
|
||||
return result;
|
||||
}
|
||||
function readOrPeek(advance) {
|
||||
return function(bytes) {
|
||||
var start = this.pointer;
|
||||
var end = start + bytes;
|
||||
|
||||
if (end > this.buffer.length) {
|
||||
throw new Error('Buffer length exceeded');
|
||||
}
|
||||
|
||||
var result = this.buffer.slice(start, end);
|
||||
|
||||
if (advance) {
|
||||
this.pointer = end;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
};
|
||||
|
||||
SerializedObject.prototype.read = readOrPeek(true);
|
||||
|
||||
SerializedObject.prototype.to_bits = function ()
|
||||
{
|
||||
SerializedObject.prototype.peek = readOrPeek(false);
|
||||
|
||||
SerializedObject.prototype.to_bits = function() {
|
||||
return sjcl.codec.bytes.toBits(this.buffer);
|
||||
};
|
||||
|
||||
SerializedObject.prototype.to_hex = function () {
|
||||
SerializedObject.prototype.to_hex = function() {
|
||||
return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase();
|
||||
};
|
||||
|
||||
SerializedObject.prototype.serialize = function (typedef, obj)
|
||||
{
|
||||
SerializedObject.prototype.to_json = function() {
|
||||
var old_pointer = this.pointer;
|
||||
this.resetPointer();
|
||||
var output = { };
|
||||
|
||||
while (this.pointer < this.buffer.length) {
|
||||
var key_and_value = stypes.parse(this);
|
||||
var key = key_and_value[0];
|
||||
var value = key_and_value[1];
|
||||
output[key] = SerializedObject.jsonify_structure(value, key);
|
||||
}
|
||||
|
||||
this.pointer = old_pointer;
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
SerializedObject.jsonify_structure = function(structure, field_name) {
|
||||
var output;
|
||||
|
||||
switch (typeof structure) {
|
||||
case 'number':
|
||||
switch (field_name) {
|
||||
case 'LedgerEntryType':
|
||||
output = LEDGER_ENTRY_TYPES[structure];
|
||||
break;
|
||||
case 'TransactionResult':
|
||||
output = TRANSACTION_RESULTS[structure];
|
||||
break;
|
||||
case 'TransactionType':
|
||||
output = TRANSACTION_TYPES[structure];
|
||||
break;
|
||||
default:
|
||||
output = structure;
|
||||
}
|
||||
break;
|
||||
case 'object':
|
||||
if (structure === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (typeof structure.to_json === 'function') {
|
||||
output = structure.to_json();
|
||||
} else if (structure instanceof BigInteger) {
|
||||
output = structure.toString(16).toUpperCase();
|
||||
} else {
|
||||
//new Array or Object
|
||||
output = new structure.constructor();
|
||||
|
||||
var keys = Object.keys(structure);
|
||||
|
||||
for (var i=0, l=keys.length; i<l; i++) {
|
||||
var key = keys[i];
|
||||
output[key] = SerializedObject.jsonify_structure(structure[key], key);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
output = structure;
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
SerializedObject.prototype.serialize = function(typedef, obj) {
|
||||
// Serialize object without end marker
|
||||
stypes.Object.serialize(this, obj, true);
|
||||
|
||||
// ST: Old serialization
|
||||
/*
|
||||
// Ensure canonical order
|
||||
typedef = SerializedObject._sort_typedef(typedef.slice());
|
||||
typedef = SerializedObject.sort_typedef(typedef);
|
||||
|
||||
// Serialize fields
|
||||
for (var i = 0, l = typedef.length; i < l; i++) {
|
||||
var spec = typedef[i];
|
||||
this.serialize_field(spec, obj);
|
||||
for (var i=0, l=typedef.length; i<l; i++) {
|
||||
this.serialize_field(typedef[i], obj);
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
||||
SerializedObject.prototype.signing_hash = function (prefix)
|
||||
{
|
||||
SerializedObject.prototype.hash = function(prefix) {
|
||||
var sign_buffer = new SerializedObject();
|
||||
stypes.Int32.serialize(sign_buffer, prefix);
|
||||
|
||||
// Add hashing prefix
|
||||
if ("undefined" !== typeof prefix) {
|
||||
stypes.Int32.serialize(sign_buffer, prefix);
|
||||
}
|
||||
|
||||
// Copy buffer to temporary buffer
|
||||
sign_buffer.append(this.buffer);
|
||||
return sign_buffer.hash_sha512_half();
|
||||
|
||||
// XXX We need a proper Buffer class then Crypt could accept that
|
||||
var bits = sjcl.codec.bytes.toBits(sign_buffer.buffer);
|
||||
return Crypt.hashSha512Half(bits);
|
||||
};
|
||||
|
||||
SerializedObject.prototype.hash_sha512_half = function ()
|
||||
{
|
||||
var bits = sjcl.codec.bytes.toBits(this.buffer),
|
||||
hash = sjcl.bitArray.bitSlice(sjcl.hash.sha512.hash(bits), 0, 256);
|
||||
// DEPRECATED
|
||||
SerializedObject.prototype.signing_hash = SerializedObject.prototype.hash;
|
||||
|
||||
return UInt256.from_hex(sjcl.codec.hex.fromBits(hash));
|
||||
};
|
||||
|
||||
SerializedObject.prototype.serialize_field = function (spec, obj)
|
||||
{
|
||||
spec = spec.slice();
|
||||
|
||||
var name = spec.shift(),
|
||||
presence = spec.shift(),
|
||||
field_id = spec.shift(),
|
||||
Type = spec.shift();
|
||||
|
||||
if ("undefined" !== typeof obj[name]) {
|
||||
//console.log(name, Type.id, field_id);
|
||||
this.append(SerializedObject.get_field_header(Type.id, field_id));
|
||||
SerializedObject.prototype.serialize_field = function(spec, obj) {
|
||||
var name = spec[0];
|
||||
var presence = spec[1];
|
||||
var field_id = spec[2];
|
||||
var Type = stypes[spec[3]];
|
||||
|
||||
if (typeof obj[name] !== 'undefined') {
|
||||
// ST: Old serialization code
|
||||
//this.append(SerializedObject.get_field_header(Type.id, field_id));
|
||||
try {
|
||||
Type.serialize(this, obj[name]);
|
||||
// ST: Old serialization code
|
||||
//Type.serialize(this, obj[name]);
|
||||
stypes.serialize(this, name, obj[name]);
|
||||
} catch (e) {
|
||||
// Add field name to message and rethrow
|
||||
e.message = "Error serializing '"+name+"': "+e.message;
|
||||
e.message = 'Error serializing "' + name + '": ' + e.message;
|
||||
throw e;
|
||||
}
|
||||
} else if (presence === binformat.REQUIRED) {
|
||||
throw new Error('Missing required field '+name);
|
||||
throw new Error('Missing required field ' + name);
|
||||
}
|
||||
};
|
||||
|
||||
SerializedObject.get_field_header = function (type_id, field_id)
|
||||
{
|
||||
var buffer = [0];
|
||||
if (type_id > 0xf) buffer.push(type_id & 0xff);
|
||||
else buffer[0] += (type_id & 0xf) << 4;
|
||||
SerializedObject.get_field_header = function(type_id, field_id) {
|
||||
var buffer = [ 0 ];
|
||||
|
||||
if (field_id > 0xf) buffer.push(field_id & 0xff);
|
||||
else buffer[0] += field_id & 0xf;
|
||||
if (type_id > 0xF) {
|
||||
buffer.push(type_id & 0xFF);
|
||||
} else {
|
||||
buffer[0] += (type_id & 0xF) << 4;
|
||||
}
|
||||
|
||||
if (field_id > 0xF) {
|
||||
buffer.push(field_id & 0xFF);
|
||||
} else {
|
||||
buffer[0] += field_id & 0xF;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
};
|
||||
|
||||
function sort_field_compare(a, b) {
|
||||
// Sort by type id first, then by field id
|
||||
return a[3].id !== b[3].id ?
|
||||
a[3].id - b[3].id :
|
||||
a[2] - b[2];
|
||||
};
|
||||
SerializedObject._sort_typedef = function (typedef) {
|
||||
SerializedObject.sort_typedef = function(typedef) {
|
||||
assert(Array.isArray(typedef));
|
||||
|
||||
function sort_field_compare(a, b) {
|
||||
// Sort by type id first, then by field id
|
||||
return a[3] !== b[3] ? stypes[a[3]].id - stypes[b[3]].id : a[2] - b[2];
|
||||
};
|
||||
|
||||
return typedef.sort(sort_field_compare);
|
||||
};
|
||||
|
||||
SerializedObject.lookup_type_tx = function (id) {
|
||||
for (var i in binformat.tx) {
|
||||
if (!binformat.tx.hasOwnProperty(i)) continue;
|
||||
SerializedObject.lookup_type_tx = function(id) {
|
||||
assert.strictEqual(typeof id, 'number');
|
||||
return TRANSACTION_TYPES[id];
|
||||
};
|
||||
|
||||
if (binformat.tx[i][0] === id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
SerializedObject.lookup_type_le = function (id) {
|
||||
assert(typeof id === 'number');
|
||||
return LEDGER_ENTRY_TYPES[id];
|
||||
};
|
||||
|
||||
exports.SerializedObject = SerializedObject;
|
||||
|
||||
@@ -6,29 +6,52 @@
|
||||
* SerializedObject.parse() or SerializedObject.serialize().
|
||||
*/
|
||||
|
||||
var extend = require('extend'),
|
||||
utils = require('./utils'),
|
||||
sjcl = require('../../../build/sjcl');
|
||||
var assert = require('assert');
|
||||
var extend = require('extend');
|
||||
var binformat = require('./binformat');
|
||||
var utils = require('./utils');
|
||||
var sjcl = utils.sjcl;
|
||||
|
||||
var amount = require('./amount'),
|
||||
UInt128 = require('./uint128').UInt128,
|
||||
UInt160 = require('./uint160').UInt160,
|
||||
UInt256 = require('./uint256').UInt256,
|
||||
Amount = amount.Amount,
|
||||
Currency= amount.Currency;
|
||||
var UInt128 = require('./uint128').UInt128;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var Base = require('./base').Base;
|
||||
|
||||
var amount = require('./amount');
|
||||
var Amount = amount.Amount;
|
||||
var Currency = amount.Currency;
|
||||
|
||||
// Shortcuts
|
||||
var hex = sjcl.codec.hex,
|
||||
bytes = sjcl.codec.bytes;
|
||||
var hex = sjcl.codec.hex;
|
||||
var bytes = sjcl.codec.bytes;
|
||||
|
||||
var jsbn = require('./jsbn');
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
|
||||
var SerializedType = function (methods) {
|
||||
extend(this, methods);
|
||||
};
|
||||
|
||||
function isNumber(val) {
|
||||
return typeof val === 'number' && isFinite(val);
|
||||
};
|
||||
|
||||
function isString(val) {
|
||||
return typeof val === 'string';
|
||||
};
|
||||
|
||||
function isHexInt64String(val) {
|
||||
return isString(val) && /^[0-9A-F]{0,16}$/i.test(val);
|
||||
};
|
||||
|
||||
function isCurrencyString(val) {
|
||||
return isString(val) && /^[A-Z0-9]{3}$/.test(val);
|
||||
};
|
||||
|
||||
function isBigInteger(val) {
|
||||
return val instanceof BigInteger;
|
||||
};
|
||||
|
||||
function serialize_hex(so, hexData, noLength) {
|
||||
var byteData = bytes.fromBits(hex.toBits(hexData));
|
||||
if (!noLength) {
|
||||
@@ -37,84 +60,95 @@ function serialize_hex(so, hexData, noLength) {
|
||||
so.append(byteData);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* parses bytes as hex
|
||||
*/
|
||||
function convert_bytes_to_hex (byte_array) {
|
||||
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array));
|
||||
}
|
||||
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array)).toUpperCase();
|
||||
};
|
||||
|
||||
SerializedType.serialize_varint = function (so, val) {
|
||||
if (val < 0) {
|
||||
throw new Error("Variable integers are unsigned.");
|
||||
throw new Error('Variable integers are unsigned.');
|
||||
}
|
||||
|
||||
if (val <= 192) {
|
||||
so.append([val]);
|
||||
} else if (val <= 12,480) {
|
||||
} else if (val <= 12480) {
|
||||
val -= 193;
|
||||
so.append([193 + (val >>> 8), val & 0xff]);
|
||||
} else if (val <= 918744) {
|
||||
val -= 12481;
|
||||
so.append([
|
||||
241 + (val >>> 16),
|
||||
val >>> 8 & 0xff,
|
||||
val & 0xff
|
||||
]);
|
||||
} else throw new Error("Variable integer overflow.");
|
||||
so.append([ 241 + (val >>> 16), val >>> 8 & 0xff, val & 0xff ]);
|
||||
} else {
|
||||
throw new Error('Variable integer overflow.');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
SerializedType.parse_varint = function (so) {
|
||||
SerializedType.prototype.parse_varint = function (so) {
|
||||
var b1 = so.read(1)[0], b2, b3;
|
||||
var result;
|
||||
|
||||
if (b1 > 254) {
|
||||
throw new Error('Invalid varint length indicator');
|
||||
}
|
||||
|
||||
if (b1 <= 192) {
|
||||
return b1;
|
||||
result = b1;
|
||||
} else if (b1 <= 240) {
|
||||
b2 = so.read(1)[0];
|
||||
return 193 + (b1-193)*256 + b2;
|
||||
result = 193 + (b1 - 193) * 256 + b2;
|
||||
} else if (b1 <= 254) {
|
||||
b2 = so.read(1)[0];
|
||||
b3 = so.read(1)[0];
|
||||
return 12481 + (b1-241)*65536 + b2*256 + b3
|
||||
}
|
||||
else {
|
||||
throw new Error("Invalid varint length indicator");
|
||||
result = 12481 + (b1 - 241) * 65536 + b2 * 256 + b3;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// In the following, we assume that the inputs are in the proper range. Is this correct?
|
||||
|
||||
// Helper functions for 1-, 2-, and 4-byte integers.
|
||||
|
||||
/**
|
||||
* Convert an integer value into an array of bytes.
|
||||
*
|
||||
* The result is appended to the serialized object ("so").
|
||||
* The result is appended to the serialized object ('so').
|
||||
*/
|
||||
function append_byte_array(so, val, bytes) {
|
||||
if ("number" !== typeof val) {
|
||||
throw new Error("Integer is not a number");
|
||||
if (!isNumber(val)) {
|
||||
throw new Error('Value is not a number', bytes);
|
||||
}
|
||||
if (val < 0 || val >= (Math.pow(256, bytes))) {
|
||||
throw new Error("Integer out of bounds");
|
||||
}
|
||||
var newBytes = [];
|
||||
for (var i=0; i<bytes; i++) {
|
||||
newBytes.unshift(val >>> (i*8) & 0xff);
|
||||
}
|
||||
so.append(newBytes);
|
||||
}
|
||||
|
||||
// Convert a certain number of bytes from the serialized object ("so") into an integer.
|
||||
if (val < 0 || val >= Math.pow(256, bytes)) {
|
||||
throw new Error('Value out of bounds');
|
||||
}
|
||||
|
||||
var newBytes = [ ];
|
||||
|
||||
for (var i=0; i<bytes; i++) {
|
||||
newBytes.unshift(val >>> (i * 8) & 0xff);
|
||||
}
|
||||
|
||||
so.append(newBytes);
|
||||
};
|
||||
|
||||
// Convert a certain number of bytes from the serialized object ('so') into an integer.
|
||||
function readAndSum(so, bytes) {
|
||||
var sum = 0;
|
||||
for (var i = 0; i<bytes; i++) {
|
||||
sum += (so.read(1)[0] << (8*(bytes-1-i)) );
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
if (bytes > 4) {
|
||||
throw new Error('This function only supports up to four bytes.');
|
||||
}
|
||||
|
||||
for (var i=0; i<bytes; i++) {
|
||||
var byte = so.read(1)[0];
|
||||
sum += (byte << (8 * (bytes - i - 1)));
|
||||
}
|
||||
|
||||
// Convert to unsigned integer
|
||||
return sum >>> 0;
|
||||
};
|
||||
|
||||
var STInt8 = exports.Int8 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
@@ -125,83 +159,83 @@ var STInt8 = exports.Int8 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STInt8.id = 16;
|
||||
|
||||
var STInt16 = exports.Int16 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
append_byte_array(so, val, 2);
|
||||
/*so.append([
|
||||
val >>> 8 & 0xff,
|
||||
val & 0xff
|
||||
]);*/
|
||||
},
|
||||
parse: function (so) {
|
||||
return readAndSum(so, 2);
|
||||
}
|
||||
});
|
||||
|
||||
STInt16.id = 1;
|
||||
|
||||
var STInt32 = exports.Int32 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
append_byte_array(so, val, 4)
|
||||
/*so.append([
|
||||
val >>> 24 & 0xff,
|
||||
val >>> 16 & 0xff,
|
||||
val >>> 8 & 0xff,
|
||||
val & 0xff
|
||||
]);*/
|
||||
append_byte_array(so, val, 4);
|
||||
},
|
||||
parse: function (so) {
|
||||
return readAndSum(so, 4);
|
||||
}
|
||||
});
|
||||
|
||||
STInt32.id = 2;
|
||||
|
||||
var STInt64 = exports.Int64 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var bigNumObject;
|
||||
if ("number" === typeof val) {
|
||||
|
||||
if (isNumber(val)) {
|
||||
val = Math.floor(val);
|
||||
if (val < 0) {
|
||||
throw new Error("Negative value for unsigned Int64 is invalid.");
|
||||
throw new Error('Negative value for unsigned Int64 is invalid.');
|
||||
}
|
||||
bigNumObject = new BigInteger(""+val, 10);
|
||||
} else if ("string" === typeof val) {
|
||||
if (!/^[0-9A-F]{0,16}$/i.test(val)) {
|
||||
throw new Error("Not a valid hex Int64.");
|
||||
bigNumObject = new BigInteger(String(val), 10);
|
||||
} else if (isString(val)) {
|
||||
if (!isHexInt64String(val)) {
|
||||
throw new Error('Not a valid hex Int64.');
|
||||
}
|
||||
bigNumObject = new BigInteger(val, 16);
|
||||
} else if (val instanceof BigInteger) {
|
||||
} else if (isBigInteger(val)) {
|
||||
if (val.compareTo(BigInteger.ZERO) < 0) {
|
||||
throw new Error("Negative value for unsigned Int64 is invalid.");
|
||||
throw new Error('Negative value for unsigned Int64 is invalid.');
|
||||
}
|
||||
bigNumObject = val;
|
||||
} else {
|
||||
throw new Error("Invalid type for Int64");
|
||||
throw new Error('Invalid type for Int64');
|
||||
}
|
||||
|
||||
var hex = bigNumObject.toString(16);
|
||||
|
||||
if (hex.length > 16) {
|
||||
throw new Error("Int64 is too large");
|
||||
throw new Error('Int64 is too large');
|
||||
}
|
||||
|
||||
while (hex.length < 16) {
|
||||
hex = "0" + hex;
|
||||
hex = '0' + hex;
|
||||
}
|
||||
return serialize_hex(so, hex, true); //noLength = true
|
||||
|
||||
serialize_hex(so, hex, true); //noLength = true
|
||||
},
|
||||
parse: function (so) {
|
||||
var hi = readAndSum(so, 4);
|
||||
var lo = readAndSum(so, 4);
|
||||
|
||||
var result = new BigInteger(hi);
|
||||
result.shiftLeft(32);
|
||||
result.add(lo);
|
||||
var bytes = so.read(8);
|
||||
// We need to add a 0, so if the high bit is set it won't think it's a
|
||||
// pessimistic numeric fraek. What doth lief?
|
||||
var result = new BigInteger([0].concat(bytes), 256);
|
||||
assert(result instanceof BigInteger);
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
STInt64.id = 3;
|
||||
|
||||
var STHash128 = exports.Hash128 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var hash = UInt128.from_json(val);
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error("Invalid Hash128");
|
||||
throw new Error('Invalid Hash128');
|
||||
}
|
||||
serialize_hex(so, hash.to_hex(), true); //noLength = true
|
||||
},
|
||||
@@ -210,11 +244,13 @@ var STHash128 = exports.Hash128 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STHash128.id = 4;
|
||||
|
||||
var STHash256 = exports.Hash256 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var hash = UInt256.from_json(val);
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error("Invalid Hash256");
|
||||
throw new Error('Invalid Hash256');
|
||||
}
|
||||
serialize_hex(so, hash.to_hex(), true); //noLength = true
|
||||
},
|
||||
@@ -223,11 +259,13 @@ var STHash256 = exports.Hash256 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STHash256.id = 5;
|
||||
|
||||
var STHash160 = exports.Hash160 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var hash = UInt160.from_json(val);
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error("Invalid Hash160");
|
||||
throw new Error('Invalid Hash160');
|
||||
}
|
||||
serialize_hex(so, hash.to_hex(), true); //noLength = true
|
||||
},
|
||||
@@ -236,34 +274,28 @@ var STHash160 = exports.Hash160 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STHash160.id = 17;
|
||||
|
||||
// Internal
|
||||
var STCurrency = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var currency = val.to_json();
|
||||
if ("XRP" === currency) {
|
||||
serialize_hex(so, UInt160.HEX_ZERO, true);
|
||||
} else if ("string" === typeof currency && currency.length === 3) {
|
||||
var currencyCode = currency.toUpperCase(),
|
||||
currencyData = utils.arraySet(20, 0);
|
||||
serialize: function (so, val, xrp_as_ascii) {
|
||||
var currencyData = val.to_bytes();
|
||||
|
||||
if (!/^[A-Z]{3}$/.test(currencyCode) || currencyCode === "XRP" ) {
|
||||
throw new Error('Invalid currency code');
|
||||
}
|
||||
|
||||
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
|
||||
currencyData[13] = currencyCode.charCodeAt(1) & 0xff;
|
||||
currencyData[14] = currencyCode.charCodeAt(2) & 0xff;
|
||||
|
||||
so.append(currencyData);
|
||||
} else {
|
||||
if (!currencyData) {
|
||||
throw new Error('Tried to serialize invalid/unimplemented currency type.');
|
||||
}
|
||||
|
||||
so.append(currencyData);
|
||||
},
|
||||
parse: function (so) {
|
||||
var currency = Currency.from_bytes(so.read(20));
|
||||
if (!currency.is_valid()) {
|
||||
throw new Error("Invalid currency");
|
||||
}
|
||||
var bytes = so.read(20);
|
||||
var currency = Currency.from_bytes(bytes);
|
||||
// XXX Disabled check. Theoretically, the Currency class should support any
|
||||
// UInt160 value and consider it valid. But it doesn't, so for the
|
||||
// deserialization to be usable, we need to allow invalid results for now.
|
||||
//if (!currency.is_valid()) {
|
||||
// throw new Error('Invalid currency: '+convert_bytes_to_hex(bytes));
|
||||
//}
|
||||
return currency;
|
||||
}
|
||||
});
|
||||
@@ -272,11 +304,12 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var amount = Amount.from_json(val);
|
||||
if (!amount.is_valid()) {
|
||||
throw new Error("Not a valid Amount object.");
|
||||
throw new Error('Not a valid Amount object.');
|
||||
}
|
||||
|
||||
// Amount (64-bit integer)
|
||||
var valueBytes = utils.arraySet(8, 0);
|
||||
|
||||
if (amount.is_native()) {
|
||||
var valueHex = amount._value.toString(16);
|
||||
|
||||
@@ -284,8 +317,9 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
if (valueHex.length > 16) {
|
||||
throw new Error('Value out of bounds');
|
||||
}
|
||||
|
||||
while (valueHex.length < 16) {
|
||||
valueHex = "0" + valueHex;
|
||||
valueHex = '0' + valueHex;
|
||||
}
|
||||
|
||||
valueBytes = bytes.fromBits(hex.toBits(valueHex));
|
||||
@@ -293,7 +327,10 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
// Amount enforces the range correctly, but we'll clear them anyway just
|
||||
// so this code can make certain guarantees about the encoded value.
|
||||
valueBytes[0] &= 0x3f;
|
||||
if (!amount.is_negative()) valueBytes[0] |= 0x40;
|
||||
|
||||
if (!amount.is_negative()) {
|
||||
valueBytes[0] |= 0x40;
|
||||
}
|
||||
} else {
|
||||
var hi = 0, lo = 0;
|
||||
|
||||
@@ -302,12 +339,13 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
|
||||
if (!amount.is_zero()) {
|
||||
// Second bit: non-negative?
|
||||
if (!amount.is_negative()) hi |= 1 << 30;
|
||||
if (!amount.is_negative()) {
|
||||
hi |= 1 << 30;
|
||||
}
|
||||
|
||||
// Next eight bits: offset/exponent
|
||||
hi |= ((97 + amount._offset) & 0xff) << 22;
|
||||
|
||||
// Remaining 52 bits: mantissa
|
||||
// Remaining 54 bits: mantissa
|
||||
hi |= amount._value.shiftRight(32).intValue() & 0x3fffff;
|
||||
lo = amount._value.intValue() & 0xffffffff;
|
||||
}
|
||||
@@ -320,7 +358,7 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
if (!amount.is_native()) {
|
||||
// Currency (160-bit hash)
|
||||
var currency = amount.currency();
|
||||
STCurrency.serialize(so, currency);
|
||||
STCurrency.serialize(so, currency, true);
|
||||
|
||||
// Issuer (160-bit hash)
|
||||
so.append(amount.issuer().to_bytes());
|
||||
@@ -330,22 +368,24 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
var amount = new Amount();
|
||||
var value_bytes = so.read(8);
|
||||
var is_zero = !(value_bytes[0] & 0x7f);
|
||||
|
||||
for (var i=1; i<8; i++) {
|
||||
is_zero = is_zero && !value_bytes[i];
|
||||
}
|
||||
|
||||
if (value_bytes[0] & 0x80) {
|
||||
//non-native
|
||||
var currency = STCurrency.parse(so);
|
||||
var issuer_bytes = so.read(20);
|
||||
var issuer = UInt160.from_bytes(issuer_bytes);
|
||||
|
||||
issuer.set_version(Base.VER_ACCOUNT_ID);
|
||||
var offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97;
|
||||
var mantissa_bytes = value_bytes.slice(1);
|
||||
mantissa_bytes[0] &= 0x3f;
|
||||
var value = new BigInteger(mantissa_bytes, 256);
|
||||
|
||||
if (value.equals(BigInteger.ZERO) && !is_zero ) {
|
||||
throw new Error("Invalid zero representation");
|
||||
throw new Error('Invalid zero representation');
|
||||
}
|
||||
|
||||
amount._value = value;
|
||||
@@ -353,7 +393,6 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
amount._currency = currency;
|
||||
amount._issuer = issuer;
|
||||
amount._is_native = false;
|
||||
|
||||
} else {
|
||||
//native
|
||||
var integer_bytes = value_bytes.slice();
|
||||
@@ -366,10 +405,15 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
var STVL = exports.VariableLength = new SerializedType({
|
||||
STAmount.id = 6;
|
||||
|
||||
var STVL = exports.VariableLength = exports.VL = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
if ("string" === typeof val) serialize_hex(so, val);
|
||||
else throw new Error("Unknown datatype.");
|
||||
if (typeof val === 'string') {
|
||||
serialize_hex(so, val);
|
||||
} else {
|
||||
throw new Error('Unknown datatype.');
|
||||
}
|
||||
},
|
||||
parse: function (so) {
|
||||
var len = this.parse_varint(so);
|
||||
@@ -377,96 +421,335 @@ var STVL = exports.VariableLength = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STVL.id = 7;
|
||||
|
||||
var STAccount = exports.Account = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var account = UInt160.from_json(val);
|
||||
if (!account.is_valid()) {
|
||||
throw new Error('Invalid account!');
|
||||
}
|
||||
serialize_hex(so, account.to_hex());
|
||||
},
|
||||
parse: function (so) {
|
||||
var len = this.parse_varint(so);
|
||||
|
||||
if (len !== 20) {
|
||||
throw new Error("Non-standard-length account ID");
|
||||
throw new Error('Non-standard-length account ID');
|
||||
}
|
||||
|
||||
var result = UInt160.from_bytes(so.read(len));
|
||||
if (!result.is_valid()) {
|
||||
throw new Error("Invalid Account");
|
||||
result.set_version(Base.VER_ACCOUNT_ID);
|
||||
|
||||
//console.log('PARSED 160:', result.to_json());
|
||||
if (false && !result.is_valid()) {
|
||||
throw new Error('Invalid Account');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
STAccount.id = 8;
|
||||
|
||||
var STPathSet = exports.PathSet = new SerializedType({
|
||||
typeBoundary: 0xff,
|
||||
typeEnd: 0x00,
|
||||
typeAccount: 0x01,
|
||||
typeCurrency: 0x10,
|
||||
typeIssuer: 0x20,
|
||||
typeBoundary: 0xff,
|
||||
typeEnd: 0x00,
|
||||
typeAccount: 0x01,
|
||||
typeCurrency: 0x10,
|
||||
typeIssuer: 0x20,
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
for (var i = 0, l = val.length; i < l; i++) {
|
||||
for (var i=0, l=val.length; i<l; i++) {
|
||||
// Boundary
|
||||
if (i) STInt8.serialize(so, this.typeBoundary);
|
||||
if (i) {
|
||||
STInt8.serialize(so, this.typeBoundary);
|
||||
}
|
||||
|
||||
for (var j = 0, l2 = val[i].length; j < l2; j++) {
|
||||
for (var j=0, l2=val[i].length; j<l2; j++) {
|
||||
var entry = val[i][j];
|
||||
|
||||
//if (entry.hasOwnProperty('_value')) {entry = entry._value;}
|
||||
var type = 0;
|
||||
|
||||
if (entry.account) type |= this.typeAccount;
|
||||
if (entry.currency) type |= this.typeCurrency;
|
||||
if (entry.issuer) type |= this.typeIssuer;
|
||||
if (entry.account) {
|
||||
type |= this.typeAccount;
|
||||
}
|
||||
if (entry.currency) {
|
||||
type |= this.typeCurrency;
|
||||
}
|
||||
if (entry.issuer) {
|
||||
type |= this.typeIssuer;
|
||||
}
|
||||
|
||||
STInt8.serialize(so, type);
|
||||
|
||||
if (entry.account) {
|
||||
so.append(UInt160.from_json(entry.account).to_bytes());
|
||||
}
|
||||
|
||||
if (entry.currency) {
|
||||
var currency = Currency.from_json(entry.currency);
|
||||
var currency = Currency.from_json(entry.currency, entry.non_native);
|
||||
STCurrency.serialize(so, currency);
|
||||
}
|
||||
|
||||
if (entry.issuer) {
|
||||
so.append(UInt160.from_json(entry.issuer).to_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
STInt8.serialize(so, this.typeEnd);
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing PathSet not implemented");
|
||||
// should return a list of lists:
|
||||
/*
|
||||
[
|
||||
[entry, entry],
|
||||
[entry, entry, entry],
|
||||
[entry],
|
||||
[]
|
||||
]
|
||||
|
||||
each entry has one or more of the following attributes: amount, currency, issuer.
|
||||
*/
|
||||
|
||||
var path_list = [];
|
||||
var current_path = [];
|
||||
var tag_byte;
|
||||
|
||||
while ((tag_byte = so.read(1)[0]) !== this.typeEnd) {
|
||||
//TODO: try/catch this loop, and catch when we run out of data without reaching the end of the data structure.
|
||||
//Now determine: is this an end, boundary, or entry-begin-tag?
|
||||
//console.log('Tag byte:', tag_byte);
|
||||
if (tag_byte === this.typeBoundary) {
|
||||
//console.log('Boundary');
|
||||
if (current_path) { //close the current path, if there is one,
|
||||
path_list.push(current_path);
|
||||
}
|
||||
current_path = [ ]; //and start a new one.
|
||||
continue;
|
||||
}
|
||||
|
||||
//It's an entry-begin tag.
|
||||
//console.log('It's an entry-begin tag.');
|
||||
var entry = {};
|
||||
|
||||
if (tag_byte & this.typeAccount) {
|
||||
//console.log('entry.account');
|
||||
/*var bta = so.read(20);
|
||||
console.log('BTA:', bta);*/
|
||||
entry.account = STHash160.parse(so);
|
||||
entry.account.set_version(Base.VER_ACCOUNT_ID);
|
||||
}
|
||||
if (tag_byte & this.typeCurrency) {
|
||||
//console.log('entry.currency');
|
||||
entry.currency = STCurrency.parse(so);
|
||||
if (entry.currency.to_json() === 'XRP' && !entry.currency.is_native()) {
|
||||
entry.non_native = true;
|
||||
}
|
||||
}
|
||||
if (tag_byte & this.typeIssuer) {
|
||||
//console.log('entry.issuer');
|
||||
entry.issuer = STHash160.parse(so);
|
||||
// Enable and set correct type of base-58 encoding
|
||||
entry.issuer.set_version(Base.VER_ACCOUNT_ID);
|
||||
//console.log('DONE WITH ISSUER!');
|
||||
}
|
||||
|
||||
if (entry.account || entry.currency || entry.issuer) {
|
||||
current_path.push(entry);
|
||||
} else {
|
||||
throw new Error('Invalid path entry'); //It must have at least something in it.
|
||||
}
|
||||
}
|
||||
|
||||
if (current_path) {
|
||||
//close the current path, if there is one,
|
||||
path_list.push(current_path);
|
||||
}
|
||||
|
||||
return path_list;
|
||||
}
|
||||
});
|
||||
|
||||
STPathSet.id = 18;
|
||||
|
||||
var STVector256 = exports.Vector256 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Vector256 not implemented");
|
||||
serialize: function (so, val) { //Assume val is an array of STHash256 objects.
|
||||
var length_as_varint = SerializedType.serialize_varint(so, val.length * 32);
|
||||
for (var i=0, l=val.length; i<l; i++) {
|
||||
STHash256.serialize(so, val[i]);
|
||||
}
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Vector256 not implemented");
|
||||
var length = this.parse_varint(so);
|
||||
var output = [];
|
||||
// length is number of bytes not number of Hash256
|
||||
for (var i=0; i<length / 32; i++) {
|
||||
output.push(STHash256.parse(so));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
});
|
||||
|
||||
STVector256.id = 19;
|
||||
|
||||
exports.serialize = exports.serialize_whatever = serialize;
|
||||
|
||||
function serialize(so, field_name, value) {
|
||||
//so: a byte-stream to serialize into.
|
||||
//field_name: a string for the field name ('LedgerEntryType' etc.)
|
||||
//value: the value of that field.
|
||||
var field_coordinates = binformat.fieldsInverseMap[field_name];
|
||||
var type_bits = field_coordinates[0];
|
||||
var field_bits = field_coordinates[1];
|
||||
var tag_byte = (type_bits < 16 ? type_bits << 4 : 0) | (field_bits < 16 ? field_bits : 0);
|
||||
|
||||
if (field_name === 'LedgerEntryType' && 'string' === typeof value) {
|
||||
value = binformat.ledger[value][0];
|
||||
}
|
||||
|
||||
if (field_name === 'TransactionResult' && 'string' === typeof value) {
|
||||
value = binformat.ter[value];
|
||||
}
|
||||
|
||||
STInt8.serialize(so, tag_byte);
|
||||
|
||||
if (type_bits >= 16) {
|
||||
STInt8.serialize(so, type_bits);
|
||||
}
|
||||
|
||||
if (field_bits >= 16) {
|
||||
STInt8.serialize(so, field_bits);
|
||||
}
|
||||
|
||||
// Get the serializer class (ST...) for a field based on the type bits.
|
||||
var serialized_object_type = exports[binformat.types[type_bits]];
|
||||
//do something with val[keys] and val[keys[i]];
|
||||
|
||||
try {
|
||||
serialized_object_type.serialize(so, value);
|
||||
} catch (e) {
|
||||
e.message += ' (' + field_name + ')';
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
//Take the serialized object, figure out what type/field it is, and return the parsing of that.
|
||||
exports.parse = exports.parse_whatever = parse;
|
||||
|
||||
function parse(so) {
|
||||
var tag_byte = so.read(1)[0];
|
||||
var type_bits = tag_byte >> 4;
|
||||
|
||||
if (type_bits === 0) {
|
||||
type_bits = so.read(1)[0];
|
||||
}
|
||||
|
||||
// Get the parser class (ST...) for a field based on the type bits.
|
||||
var type = exports[binformat.types[type_bits]];
|
||||
|
||||
assert(type, 'Unknown type - header byte is 0x' + tag_byte.toString(16));
|
||||
|
||||
var field_bits = tag_byte & 0x0f;
|
||||
var field_name = (field_bits === 0)
|
||||
? field_name = binformat.fields[type_bits][so.read(1)[0]]
|
||||
: field_name = binformat.fields[type_bits][field_bits];
|
||||
|
||||
assert(field_name, 'Unknown field - header byte is 0x' + tag_byte.toString(16));
|
||||
|
||||
return [ field_name, type.parse(so) ]; //key, value
|
||||
};
|
||||
|
||||
function sort_fields(keys) {
|
||||
function sort_field_compare(a, b) {
|
||||
var a_field_coordinates = binformat.fieldsInverseMap[a];
|
||||
var a_type_bits = a_field_coordinates[0];
|
||||
var a_field_bits = a_field_coordinates[1];
|
||||
var b_field_coordinates = binformat.fieldsInverseMap[b];
|
||||
var b_type_bits = b_field_coordinates[0];
|
||||
var b_field_bits = b_field_coordinates[1];
|
||||
|
||||
// Sort by type id first, then by field id
|
||||
return a_type_bits !== b_type_bits ? a_type_bits - b_type_bits : a_field_bits - b_field_bits;
|
||||
};
|
||||
|
||||
return keys.sort(sort_field_compare);
|
||||
}
|
||||
|
||||
var STObject = exports.Object = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Object not implemented");
|
||||
serialize: function (so, val, no_marker) {
|
||||
var keys = Object.keys(val);
|
||||
|
||||
// Ignore lowercase field names - they're non-serializable fields by
|
||||
// convention.
|
||||
keys = keys.filter(function (key) {
|
||||
return key[0] !== key[0].toLowerCase();
|
||||
});
|
||||
|
||||
keys.forEach(function (key) {
|
||||
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
|
||||
throw new Error('JSON contains unknown field: "' + key + '"');
|
||||
}
|
||||
});
|
||||
|
||||
// Sort fields
|
||||
keys = sort_fields(keys);
|
||||
|
||||
for (var i=0; i<keys.length; i++) {
|
||||
serialize(so, keys[i], val[keys[i]]);
|
||||
}
|
||||
|
||||
if (!no_marker) {
|
||||
//Object ending marker
|
||||
STInt8.serialize(so, 0xe1);
|
||||
}
|
||||
},
|
||||
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Object not implemented");
|
||||
var output = {};
|
||||
while (so.peek(1)[0] !== 0xe1) {
|
||||
var keyval = parse(so);
|
||||
output[keyval[0]] = keyval[1];
|
||||
}
|
||||
so.read(1);
|
||||
return output;
|
||||
}
|
||||
});
|
||||
|
||||
STObject.id = 14;
|
||||
|
||||
var STArray = exports.Array = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Array not implemented");
|
||||
for (var i=0, l=val.length; i<l; i++) {
|
||||
var keys = Object.keys(val[i]);
|
||||
|
||||
if (keys.length !== 1) {
|
||||
throw Error('Cannot serialize an array containing non-single-key objects');
|
||||
}
|
||||
|
||||
var field_name = keys[0];
|
||||
var value = val[i][field_name];
|
||||
serialize(so, field_name, value);
|
||||
}
|
||||
|
||||
//Array ending marker
|
||||
STInt8.serialize(so, 0xf1);
|
||||
},
|
||||
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Array not implemented");
|
||||
var output = [ ];
|
||||
|
||||
while (so.peek(1)[0] !== 0xf1) {
|
||||
var keyval = parse(so);
|
||||
var obj = { };
|
||||
obj[keyval[0]] = keyval[1];
|
||||
output.push(obj);
|
||||
}
|
||||
|
||||
so.read(1);
|
||||
|
||||
return output;
|
||||
}
|
||||
});
|
||||
|
||||
STArray.id = 15;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
168
src/js/ripple/shamap.js
Normal file
168
src/js/ripple/shamap.js
Normal file
@@ -0,0 +1,168 @@
|
||||
var util = require('util');
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var stypes = require('./serializedtypes');
|
||||
var hashprefixes = require('./hashprefixes');
|
||||
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var SerializedObject = require('./serializedobject').SerializedObject;
|
||||
|
||||
function SHAMap() {
|
||||
this.root = new SHAMapTreeNodeInner(0);
|
||||
};
|
||||
|
||||
SHAMap.prototype.add_item = function(tag, node, type) {
|
||||
var node = new SHAMapTreeNodeLeaf(tag, node, type);
|
||||
this.root.add_item(tag, node);
|
||||
};
|
||||
|
||||
SHAMap.prototype.hash = function() {
|
||||
return this.root.hash();
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract class representing a node in a SHAMap tree.
|
||||
*
|
||||
* Can be either SHAMapTreeNodeInner or SHAMapTreeNodeLeaf.
|
||||
*/
|
||||
function SHAMapTreeNode() { };
|
||||
|
||||
SHAMapTreeNode.TYPE_INNER = 1;
|
||||
SHAMapTreeNode.TYPE_TRANSACTION_NM = 2;
|
||||
SHAMapTreeNode.TYPE_TRANSACTION_MD = 3;
|
||||
SHAMapTreeNode.TYPE_ACCOUNT_STATE = 4;
|
||||
|
||||
/**
|
||||
* @param tag {String} 64 hexadecimal characters
|
||||
*/
|
||||
SHAMapTreeNode.prototype.add_item = function(tag, node) {
|
||||
throw new Error('Called unimplemented virtual method SHAMapTreeNode#add_item.');
|
||||
};
|
||||
|
||||
SHAMapTreeNode.prototype.hash = function() {
|
||||
throw new Error('Called unimplemented virtual method SHAMapTreeNode#hash.');
|
||||
};
|
||||
|
||||
/**
|
||||
* Inner (non-leaf) node in a SHAMap tree.
|
||||
*/
|
||||
function SHAMapTreeNodeInner(depth) {
|
||||
SHAMapTreeNode.call(this);
|
||||
|
||||
this.leaves = {};
|
||||
|
||||
this.type = SHAMapTreeNode.INNER;
|
||||
this.depth = depth == null ? 0 : depth;
|
||||
|
||||
this.empty = true;
|
||||
}
|
||||
|
||||
util.inherits(SHAMapTreeNodeInner, SHAMapTreeNode);
|
||||
|
||||
/**
|
||||
* @param tag {String} (equates to a ledger entries `index`)
|
||||
*/
|
||||
SHAMapTreeNodeInner.prototype.add_item = function (tag, node) {
|
||||
var depth = this.depth;
|
||||
var existing_node = this.get_node(tag[depth]);
|
||||
|
||||
if (existing_node) {
|
||||
// A node already exists in this slot
|
||||
if (existing_node instanceof SHAMapTreeNodeInner) {
|
||||
// There is an inner node, so we need to go deeper
|
||||
existing_node.add_item(tag, node);
|
||||
} else if (existing_node.tag === tag) {
|
||||
// Collision
|
||||
throw new Error('Tried to add a node to a SHAMap that was already in there.');
|
||||
} else {
|
||||
// Turn it into an inner node
|
||||
var new_inner_node = new SHAMapTreeNodeInner(depth + 1);
|
||||
|
||||
// Parent new and existing node
|
||||
new_inner_node.add_item(existing_node.tag, existing_node);
|
||||
new_inner_node.add_item(tag, node);
|
||||
|
||||
// And place the newly created inner node in the slot
|
||||
this.set_node(tag[depth], new_inner_node);
|
||||
}
|
||||
} else {
|
||||
// Neat, we have a nice open spot for the new node
|
||||
this.set_node(tag[depth], node);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Overwrite the node that is currently in a given slot.
|
||||
*/
|
||||
SHAMapTreeNodeInner.prototype.set_node = function(slot, node) {
|
||||
this.leaves[slot] = node;
|
||||
this.empty = false;
|
||||
};
|
||||
|
||||
SHAMapTreeNodeInner.prototype.get_node = function(slot) {
|
||||
return this.leaves[slot];
|
||||
};
|
||||
|
||||
SHAMapTreeNodeInner.prototype.hash = function() {
|
||||
if (this.empty) {
|
||||
return UInt256.from_hex(UInt256.HEX_ZERO);
|
||||
}
|
||||
|
||||
var hash_buffer = new SerializedObject();
|
||||
var buffer = [ ];
|
||||
|
||||
for (var i=0; i<16; i++) {
|
||||
var leafHash = UInt256.from_hex(UInt256.HEX_ZERO);
|
||||
var slot = i.toString(16).toUpperCase();
|
||||
|
||||
if (typeof this.leaves[slot] === 'object') {
|
||||
leafHash = this.leaves[slot].hash();
|
||||
}
|
||||
|
||||
hash_buffer.append(leafHash.to_bytes());
|
||||
}
|
||||
|
||||
var hash = hash_buffer.hash(hashprefixes.HASH_INNER_NODE);
|
||||
|
||||
return UInt256.from_bits(hash);
|
||||
};
|
||||
|
||||
/**
|
||||
* Leaf node in a SHAMap tree.
|
||||
*/
|
||||
function SHAMapTreeNodeLeaf(tag, node, type) {
|
||||
SHAMapTreeNode.call(this);
|
||||
|
||||
if (typeof tag !== 'string') {
|
||||
throw new Error('Tag is unexpected type.');
|
||||
}
|
||||
|
||||
this.tag = tag;
|
||||
this.tag_bytes = UInt256.from_hex(this.tag).to_bytes();
|
||||
this.type = type;
|
||||
this.node = node;
|
||||
};
|
||||
|
||||
util.inherits(SHAMapTreeNodeLeaf, SHAMapTreeNode);
|
||||
|
||||
SHAMapTreeNodeLeaf.prototype.hash = function () {
|
||||
var buffer = new SerializedObject();
|
||||
switch (this.type) {
|
||||
case SHAMapTreeNode.TYPE_ACCOUNT_STATE:
|
||||
buffer.append(this.node);
|
||||
buffer.append(this.tag_bytes);
|
||||
return buffer.hash(hashprefixes.HASH_LEAF_NODE);
|
||||
case SHAMapTreeNode.TYPE_TRANSACTION_NM:
|
||||
return this.tag_bytes;
|
||||
case SHAMapTreeNode.TYPE_TRANSACTION_MD:
|
||||
buffer.append(this.node);
|
||||
buffer.append(this.tag_bytes);
|
||||
return buffer.hash(hashprefixes.HASH_TX_NODE);
|
||||
default:
|
||||
throw new Error('Tried to hash a SHAMap node of unknown type.');
|
||||
}
|
||||
};
|
||||
|
||||
exports.SHAMap = SHAMap;
|
||||
exports.SHAMapTreeNode = SHAMapTreeNode;
|
||||
exports.SHAMapTreeNodeInner = SHAMapTreeNodeInner;
|
||||
exports.SHAMapTreeNodeLeaf = SHAMapTreeNodeLeaf;
|
||||
203
src/js/ripple/signedrequest.js
Normal file
203
src/js/ripple/signedrequest.js
Normal file
@@ -0,0 +1,203 @@
|
||||
var Crypt = require('./crypt').Crypt;
|
||||
var Message = require('./message').Message;
|
||||
var parser = require("url");
|
||||
var querystring = require('querystring');
|
||||
var extend = require("extend");
|
||||
|
||||
var SignedRequest = function (config) {
|
||||
// XXX Constructor should be generalized and constructing from an Angular.js
|
||||
// $http config should be a SignedRequest.from... utility method.
|
||||
this.config = extend(true, {}, config);
|
||||
if (!this.config.data) this.config.data = {};
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a string from request parameters that
|
||||
* will be used to sign a request
|
||||
* @param {Object} parsed - parsed url
|
||||
* @param {Object} date
|
||||
* @param {Object} mechanism - type of signing
|
||||
*/
|
||||
SignedRequest.prototype.getStringToSign = function (parsed, date, mechanism) {
|
||||
// XXX This method doesn't handle signing GET requests correctly. The data
|
||||
// field will be merged into the search string, not the request body.
|
||||
|
||||
// Sort the properties of the JSON object into canonical form
|
||||
var canonicalData = JSON.stringify(copyObjectWithSortedKeys(this.config.data));
|
||||
|
||||
// Canonical request using Amazon's v4 signature format
|
||||
// See: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
||||
var canonicalRequest = [
|
||||
this.config.method || 'GET',
|
||||
parsed.pathname || '',
|
||||
parsed.search || '',
|
||||
// XXX Headers signing not supported
|
||||
'',
|
||||
'',
|
||||
Crypt.hashSha512(canonicalData).toLowerCase()
|
||||
].join('\n');
|
||||
|
||||
// String to sign inspired by Amazon's v4 signature format
|
||||
// See: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
|
||||
//
|
||||
// We don't have a credential scope, so we skip it.
|
||||
//
|
||||
// But that modifies the format, so the format ID is RIPPLE1, instead of AWS4.
|
||||
return [
|
||||
mechanism,
|
||||
date,
|
||||
Crypt.hashSha512(canonicalRequest).toLowerCase()
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
//prepare for signing
|
||||
function copyObjectWithSortedKeys(object) {
|
||||
if (isPlainObject(object)) {
|
||||
var newObj = {};
|
||||
var keysSorted = Object.keys(object).sort();
|
||||
var key;
|
||||
for (var i in keysSorted) {
|
||||
key = keysSorted[i];
|
||||
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
||||
newObj[key] = copyObjectWithSortedKeys(object[key]);
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
} else if (Array.isArray(object)) {
|
||||
return object.map(copyObjectWithSortedKeys);
|
||||
} else {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
//from npm extend
|
||||
function isPlainObject(obj) {
|
||||
var hasOwn = Object.prototype.hasOwnProperty;
|
||||
var toString = Object.prototype.toString;
|
||||
|
||||
if (!obj || toString.call(obj) !== '[object Object]' || obj.nodeType || obj.setInterval)
|
||||
return false;
|
||||
|
||||
var has_own_constructor = hasOwn.call(obj, 'constructor');
|
||||
var has_is_property_of_method = hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
|
||||
// Not own constructor property must be Object
|
||||
if (obj.constructor && !has_own_constructor && !has_is_property_of_method)
|
||||
return false;
|
||||
|
||||
// Own properties are enumerated firstly, so to speed up,
|
||||
// if last one is own, then all properties are own.
|
||||
var key;
|
||||
for ( key in obj ) {}
|
||||
|
||||
return key === undefined || hasOwn.call( obj, key );
|
||||
};
|
||||
|
||||
/**
|
||||
* HMAC signed request
|
||||
* @param {Object} config
|
||||
* @param {Object} auth_secret
|
||||
* @param {Object} blob_id
|
||||
*/
|
||||
SignedRequest.prototype.signHmac = function (auth_secret, blob_id) {
|
||||
var config = extend(true, {}, this.config);
|
||||
|
||||
// Parse URL
|
||||
var parsed = parser.parse(config.url);
|
||||
var date = dateAsIso8601();
|
||||
var signatureType = 'RIPPLE1-HMAC-SHA512';
|
||||
var stringToSign = this.getStringToSign(parsed, date, signatureType);
|
||||
var signature = Crypt.signString(auth_secret, stringToSign);
|
||||
|
||||
var query = querystring.stringify({
|
||||
signature: Crypt.base64ToBase64Url(signature),
|
||||
signature_date: date,
|
||||
signature_blob_id: blob_id,
|
||||
signature_type: signatureType
|
||||
});
|
||||
|
||||
config.url += (parsed.search ? '&' : '?') + query;
|
||||
return config;
|
||||
};
|
||||
|
||||
/**
|
||||
* Asymmetric signed request
|
||||
* @param {Object} config
|
||||
* @param {Object} secretKey
|
||||
* @param {Object} account
|
||||
* @param {Object} blob_id
|
||||
*/
|
||||
SignedRequest.prototype.signAsymmetric = function (secretKey, account, blob_id) {
|
||||
var config = extend(true, {}, this.config);
|
||||
|
||||
// Parse URL
|
||||
var parsed = parser.parse(config.url);
|
||||
var date = dateAsIso8601();
|
||||
var signatureType = 'RIPPLE1-ECDSA-SHA512';
|
||||
var stringToSign = this.getStringToSign(parsed, date, signatureType);
|
||||
var signature = Message.signMessage(stringToSign, secretKey);
|
||||
|
||||
var query = querystring.stringify({
|
||||
signature: Crypt.base64ToBase64Url(signature),
|
||||
signature_date: date,
|
||||
signature_blob_id: blob_id,
|
||||
signature_account: account,
|
||||
signature_type: signatureType
|
||||
});
|
||||
|
||||
config.url += (parsed.search ? '&' : '?') + query;
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
/**
|
||||
* Asymmetric signed request for vault recovery
|
||||
* @param {Object} config
|
||||
* @param {Object} secretKey
|
||||
* @param {Object} username
|
||||
*/
|
||||
SignedRequest.prototype.signAsymmetricRecovery = function (secretKey, username) {
|
||||
var config = extend(true, {}, this.config);
|
||||
|
||||
// Parse URL
|
||||
var parsed = parser.parse(config.url);
|
||||
var date = dateAsIso8601();
|
||||
var signatureType = 'RIPPLE1-ECDSA-SHA512';
|
||||
var stringToSign = this.getStringToSign(parsed, date, signatureType);
|
||||
var signature = Message.signMessage(stringToSign, secretKey);
|
||||
|
||||
var query = querystring.stringify({
|
||||
signature: Crypt.base64ToBase64Url(signature),
|
||||
signature_date: date,
|
||||
signature_username: username,
|
||||
signature_type: signatureType
|
||||
});
|
||||
|
||||
config.url += (parsed.search ? '&' : '?') + query;
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
var dateAsIso8601 = (function () {
|
||||
function pad(n) {
|
||||
return (n < 0 || n > 9 ? "" : "0") + n;
|
||||
}
|
||||
|
||||
return function dateAsIso8601() {
|
||||
var date = new Date();
|
||||
return date.getUTCFullYear() + "-" +
|
||||
pad(date.getUTCMonth() + 1) + "-" +
|
||||
pad(date.getUTCDate()) + "T" +
|
||||
pad(date.getUTCHours()) + ":" +
|
||||
pad(date.getUTCMinutes()) + ":" +
|
||||
pad(date.getUTCSeconds()) + ".000Z";
|
||||
};
|
||||
})();
|
||||
|
||||
// XXX Add methods for verifying requests
|
||||
// SignedRequest.prototype.verifySignatureHmac
|
||||
// SignedRequest.prototype.verifySignatureAsymetric
|
||||
|
||||
exports.SignedRequest = SignedRequest;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
679
src/js/ripple/transactionmanager.js
Normal file
679
src/js/ripple/transactionmanager.js
Normal file
@@ -0,0 +1,679 @@
|
||||
var util = require('util');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Transaction = require('./transaction').Transaction;
|
||||
var RippleError = require('./rippleerror').RippleError;
|
||||
var PendingQueue = require('./transactionqueue').TransactionQueue;
|
||||
var log = require('./log').internal.sub('transactionmanager');
|
||||
|
||||
/**
|
||||
* @constructor TransactionManager
|
||||
* @param {Account} account
|
||||
*/
|
||||
|
||||
function TransactionManager(account) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
this._account = account;
|
||||
this._accountID = account._account_id;
|
||||
this._remote = account._remote;
|
||||
this._nextSequence = void(0);
|
||||
this._maxFee = this._remote.max_fee;
|
||||
this._maxAttempts = this._remote.max_attempts;
|
||||
this._submissionTimeout = this._remote._submission_timeout;
|
||||
this._pending = new PendingQueue();
|
||||
|
||||
// Query remote server for next account sequence number
|
||||
this._loadSequence();
|
||||
|
||||
function transactionReceived(res) {
|
||||
var transaction = TransactionManager.normalizeTransaction(res);
|
||||
var sequence = transaction.tx_json.Sequence;
|
||||
var hash = transaction.tx_json.hash;
|
||||
|
||||
if (!transaction.validated) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._pending.addReceivedSequence(sequence);
|
||||
|
||||
// ND: we need to check against all submissions IDs
|
||||
var submission = self._pending.getSubmission(hash);
|
||||
|
||||
if (self._remote.trace) {
|
||||
log.info('transaction received:', transaction.tx_json);
|
||||
}
|
||||
|
||||
if (submission instanceof Transaction) {
|
||||
// ND: A `success` handler will `finalize` this later
|
||||
submission.emit('success', transaction);
|
||||
} else {
|
||||
self._pending.addReceivedId(hash, transaction);
|
||||
}
|
||||
};
|
||||
|
||||
this._account.on('transaction-outbound', transactionReceived);
|
||||
|
||||
this._remote.on('load_changed', this._adjustFees.bind(this));
|
||||
|
||||
function updatePendingStatus(ledger) {
|
||||
self._pending.forEach(function(pending) {
|
||||
switch (ledger.ledger_index - pending.submitIndex) {
|
||||
case 8:
|
||||
pending.emit('lost', ledger);
|
||||
break;
|
||||
case 4:
|
||||
pending.emit('missing', ledger);
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this._remote.on('ledger_closed', updatePendingStatus);
|
||||
|
||||
function remoteReconnected(callback) {
|
||||
var callback = (typeof callback === 'function') ? callback : function(){};
|
||||
|
||||
if (!self._pending.length) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
//Load account transaction history
|
||||
var options = {
|
||||
account: self._accountID,
|
||||
ledger_index_min: -1,
|
||||
ledger_index_max: -1,
|
||||
binary: true,
|
||||
parseBinary: true,
|
||||
limit: 100,
|
||||
filter: 'outbound'
|
||||
};
|
||||
|
||||
function accountTx(err, transactions) {
|
||||
if (!err && Array.isArray(transactions.transactions)) {
|
||||
transactions.transactions.forEach(transactionReceived);
|
||||
}
|
||||
|
||||
self._remote.on('ledger_closed', updatePendingStatus);
|
||||
|
||||
//Load next transaction sequence
|
||||
self._loadSequence(self._resubmit.bind(self));
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
self._remote.requestAccountTx(options, accountTx);
|
||||
|
||||
self.emit('reconnect');
|
||||
};
|
||||
|
||||
function remoteDisconnected() {
|
||||
self._remote.once('connect', remoteReconnected);
|
||||
self._remote.removeListener('ledger_closed', updatePendingStatus);
|
||||
};
|
||||
|
||||
this._remote.on('disconnect', remoteDisconnected);
|
||||
|
||||
function saveTransaction(transaction) {
|
||||
self._remote.storage.saveTransaction(transaction.summary());
|
||||
};
|
||||
|
||||
if (this._remote.storage) {
|
||||
this.on('save', saveTransaction);
|
||||
}
|
||||
};
|
||||
|
||||
util.inherits(TransactionManager, EventEmitter);
|
||||
|
||||
//Normalize transactions received from account
|
||||
//transaction stream and account_tx
|
||||
TransactionManager.normalizeTransaction = function(tx) {
|
||||
var transaction = { };
|
||||
|
||||
Object.keys(tx).forEach(function(key) {
|
||||
transaction[key] = tx[key];
|
||||
});
|
||||
|
||||
if (!tx.engine_result) {
|
||||
// account_tx
|
||||
transaction = {
|
||||
engine_result: tx.meta.TransactionResult,
|
||||
tx_json: tx.tx,
|
||||
hash: tx.tx.hash,
|
||||
ledger_index: tx.tx.ledger_index,
|
||||
meta: tx.meta,
|
||||
type: 'transaction',
|
||||
validated: true
|
||||
};
|
||||
|
||||
transaction.result = transaction.engine_result;
|
||||
transaction.result_message = transaction.engine_result_message;
|
||||
}
|
||||
|
||||
if (!transaction.metadata) {
|
||||
transaction.metadata = transaction.meta;
|
||||
}
|
||||
|
||||
if (!transaction.tx_json) {
|
||||
transaction.tx_json = transaction.transaction;
|
||||
}
|
||||
|
||||
delete transaction.transaction;
|
||||
delete transaction.mmeta;
|
||||
delete transaction.meta;
|
||||
|
||||
return transaction;
|
||||
};
|
||||
|
||||
// Transaction fees are adjusted in real-time
|
||||
TransactionManager.prototype._adjustFees = function(loadData) {
|
||||
// ND: note, that `Fee` is a component of a transactionID
|
||||
var self = this;
|
||||
|
||||
if (!this._remote.local_fee) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._pending.forEach(function(pending) {
|
||||
var oldFee = pending.tx_json.Fee;
|
||||
var newFee = pending._computeFee();
|
||||
|
||||
function maxFeeExceeded() {
|
||||
pending.once('presubmit', function() {
|
||||
pending.emit('error', 'tejMaxFeeExceeded');
|
||||
});
|
||||
};
|
||||
|
||||
if (Number(newFee) > self._maxFee) {
|
||||
return maxFeeExceeded();
|
||||
}
|
||||
|
||||
pending.tx_json.Fee = newFee;
|
||||
pending.emit('fee_adjusted', oldFee, newFee);
|
||||
|
||||
if (self._remote.trace) {
|
||||
log.info('fee adjusted:', pending.tx_json, oldFee, newFee);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//Fill an account transaction sequence
|
||||
TransactionManager.prototype._fillSequence = function(tx, callback) {
|
||||
var self = this;
|
||||
|
||||
function submitFill(sequence, callback) {
|
||||
var fill = self._remote.transaction();
|
||||
fill.account_set(self._accountID);
|
||||
fill.tx_json.Sequence = sequence;
|
||||
fill.once('submitted', callback);
|
||||
|
||||
// Secrets may be set on a per-transaction basis
|
||||
if (tx._secret) {
|
||||
fill.secret(tx._secret);
|
||||
}
|
||||
|
||||
fill.submit();
|
||||
};
|
||||
|
||||
function sequenceLoaded(err, sequence) {
|
||||
if (typeof sequence !== 'number') {
|
||||
return callback(new Error('Failed to fetch account transaction sequence'));
|
||||
}
|
||||
|
||||
var sequenceDif = tx.tx_json.Sequence - sequence;
|
||||
var submitted = 0;
|
||||
|
||||
;(function nextFill(sequence) {
|
||||
if (sequence >= tx.tx_json.Sequence) {
|
||||
return;
|
||||
}
|
||||
|
||||
submitFill(sequence, function() {
|
||||
if (++submitted === sequenceDif) {
|
||||
callback();
|
||||
} else {
|
||||
nextFill(sequence + 1);
|
||||
}
|
||||
});
|
||||
})(sequence);
|
||||
};
|
||||
|
||||
this._loadSequence(sequenceLoaded);
|
||||
};
|
||||
|
||||
TransactionManager.prototype._loadSequence = function(callback) {
|
||||
var self = this;
|
||||
|
||||
function sequenceLoaded(err, sequence) {
|
||||
if (typeof sequence === 'number') {
|
||||
self._nextSequence = sequence;
|
||||
self.emit('sequence_loaded', sequence);
|
||||
if (typeof callback === 'function') {
|
||||
callback(err, sequence);
|
||||
}
|
||||
} else {
|
||||
setTimeout(function() {
|
||||
self._loadSequence(callback);
|
||||
}, 1000 * 3);
|
||||
}
|
||||
};
|
||||
|
||||
this._account.getNextSequence(sequenceLoaded);
|
||||
};
|
||||
|
||||
TransactionManager.prototype._resubmit = function(ledgers, pending) {
|
||||
var self = this;
|
||||
var pending = pending ? [ pending ] : this._pending;
|
||||
var ledgers = Number(ledgers) || 0;
|
||||
|
||||
function resubmitTransaction(pending) {
|
||||
if (!pending || pending.finalized) {
|
||||
// Transaction has been finalized, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
var hashCached = pending.findId(self._pending._idCache);
|
||||
|
||||
if (self._remote.trace) {
|
||||
log.info('resubmit:', pending.tx_json);
|
||||
}
|
||||
|
||||
if (hashCached) {
|
||||
return pending.emit('success', hashCached);
|
||||
}
|
||||
|
||||
while (self._pending.hasSequence(pending.tx_json.Sequence)) {
|
||||
//Sequence number has been consumed by another transaction
|
||||
pending.tx_json.Sequence += 1;
|
||||
|
||||
if (self._remote.trace) {
|
||||
log.info('incrementing sequence:', pending.tx_json);
|
||||
}
|
||||
}
|
||||
|
||||
self._request(pending);
|
||||
};
|
||||
|
||||
function resubmitTransactions() {
|
||||
;(function nextTransaction(i) {
|
||||
var transaction = pending[i];
|
||||
|
||||
if (!(transaction instanceof Transaction)) {
|
||||
return;
|
||||
}
|
||||
|
||||
transaction.once('submitted', function(m) {
|
||||
transaction.emit('resubmitted', m);
|
||||
|
||||
self._loadSequence();
|
||||
|
||||
if (++i < pending.length) {
|
||||
nextTransaction(i);
|
||||
}
|
||||
});
|
||||
|
||||
resubmitTransaction(transaction);
|
||||
})(0);
|
||||
};
|
||||
|
||||
this._waitLedgers(ledgers, resubmitTransactions);
|
||||
};
|
||||
|
||||
TransactionManager.prototype._waitLedgers = function(ledgers, callback) {
|
||||
if (ledgers < 1) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var closes = 0;
|
||||
|
||||
function ledgerClosed() {
|
||||
if (++closes < ledgers) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._remote.removeListener('ledger_closed', ledgerClosed);
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
this._remote.on('ledger_closed', ledgerClosed);
|
||||
};
|
||||
|
||||
TransactionManager.prototype._request = function(tx) {
|
||||
var self = this;
|
||||
var remote = this._remote;
|
||||
|
||||
if (tx.attempts > this._maxAttempts) {
|
||||
return tx.emit('error', new RippleError('tejAttemptsExceeded'));
|
||||
}
|
||||
|
||||
if (tx.attempts > 0 && !remote.local_signing) {
|
||||
var message = ''
|
||||
+ 'It is not possible to resubmit transactions automatically safely without '
|
||||
+ 'synthesizing the transactionID locally. See `local_signing` config option';
|
||||
|
||||
return tx.emit('error', new RippleError('tejLocalSigningRequired', message));
|
||||
}
|
||||
|
||||
tx.emit('presubmit');
|
||||
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (remote.trace) {
|
||||
log.info('submit transaction:', tx.tx_json);
|
||||
}
|
||||
|
||||
function transactionProposed(message) {
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If server is honest, don't expect a final if rejected.
|
||||
message.rejected = tx.isRejected(message.engine_result_code);
|
||||
|
||||
tx.emit('proposed', message);
|
||||
};
|
||||
|
||||
function transactionFailed(message) {
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.engine_result) {
|
||||
case 'tefPAST_SEQ':
|
||||
self._resubmit(1, tx);
|
||||
break;
|
||||
case 'tefALREADY':
|
||||
if (tx.responses === tx.submissions) {
|
||||
tx.emit('error', message);
|
||||
} else {
|
||||
submitRequest.once('success', submitted);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
tx.emit('error', message);
|
||||
}
|
||||
};
|
||||
|
||||
function transactionRetry(message) {
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._fillSequence(tx, function() {
|
||||
self._resubmit(1, tx);
|
||||
});
|
||||
};
|
||||
|
||||
function transactionFeeClaimed(message) {
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
tx.emit('error', message);
|
||||
};
|
||||
|
||||
function transactionFailedLocal(message) {
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self._remote.local_fee && (message.engine_result === 'telINSUF_FEE_P')) {
|
||||
self._resubmit(2, tx);
|
||||
} else {
|
||||
submissionError(message);
|
||||
}
|
||||
};
|
||||
|
||||
function submissionError(error) {
|
||||
// Finalized (e.g. aborted) transactions must stop all activity
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (TransactionManager._isTooBusy(error)) {
|
||||
self._resubmit(1, tx);
|
||||
} else {
|
||||
self._nextSequence--;
|
||||
tx.emit('error', error);
|
||||
}
|
||||
};
|
||||
|
||||
function submitted(message) {
|
||||
// Finalized (e.g. aborted) transactions must stop all activity
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ND: If for some unknown reason our hash wasn't computed correctly this is
|
||||
// an extra measure.
|
||||
if (message.tx_json && message.tx_json.hash) {
|
||||
tx.addId(message.tx_json.hash);
|
||||
}
|
||||
|
||||
message.result = message.engine_result || '';
|
||||
|
||||
tx.result = message;
|
||||
tx.responses += 1;
|
||||
|
||||
if (remote.trace) {
|
||||
log.info('submit response:', message);
|
||||
}
|
||||
|
||||
tx.emit('submitted', message);
|
||||
|
||||
switch (message.result.slice(0, 3)) {
|
||||
case 'tes':
|
||||
transactionProposed(message);
|
||||
break;
|
||||
case 'tec':
|
||||
transactionFeeClaimed(message);
|
||||
break;
|
||||
case 'ter':
|
||||
transactionRetry(message);
|
||||
break;
|
||||
case 'tef':
|
||||
transactionFailed(message);
|
||||
break;
|
||||
case 'tel':
|
||||
transactionFailedLocal(message);
|
||||
break;
|
||||
default:
|
||||
// tem
|
||||
submissionError(message);
|
||||
}
|
||||
};
|
||||
|
||||
var submitRequest = remote.requestSubmit();
|
||||
|
||||
submitRequest.once('error', submitted);
|
||||
submitRequest.once('success', submitted);
|
||||
|
||||
function prepareSubmit() {
|
||||
if (remote.local_signing) {
|
||||
// TODO: We are serializing twice, when we could/should be feeding the
|
||||
// tx_blob to `tx.hash()` which rebuilds it to sign it.
|
||||
submitRequest.tx_blob(tx.serialize().to_hex());
|
||||
|
||||
// ND: ecdsa produces a random `TxnSignature` field value, a component of
|
||||
// the hash. Attempting to identify a transaction via a hash synthesized
|
||||
// locally while using remote signing is inherently flawed.
|
||||
tx.addId(tx.hash());
|
||||
} else {
|
||||
// ND: `build_path` is completely ignored when doing local signing as
|
||||
// `Paths` is a component of the signed blob, the `tx_blob` is signed,
|
||||
// sealed and delivered, and the txn unmodified.
|
||||
// TODO: perhaps an exception should be raised if build_path is attempted
|
||||
// while local signing
|
||||
submitRequest.build_path(tx._build_path);
|
||||
submitRequest.secret(tx._secret);
|
||||
submitRequest.tx_json(tx.tx_json);
|
||||
}
|
||||
|
||||
if (tx._server) {
|
||||
submitRequest.server = tx._server;
|
||||
}
|
||||
|
||||
submitTransaction();
|
||||
};
|
||||
|
||||
function requestTimeout() {
|
||||
// ND: What if the response is just slow and we get a response that
|
||||
// `submitted` above will cause to have concurrent resubmit logic streams?
|
||||
// It's simpler to just mute handlers and look out for finalized
|
||||
// `transaction` messages.
|
||||
|
||||
// ND: We should audit the code for other potential multiple resubmit
|
||||
// streams. Connection/reconnection could be one? That's why it's imperative
|
||||
// that ALL transactionIDs sent over network are tracked.
|
||||
|
||||
// Finalized (e.g. aborted) transactions must stop all activity
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
tx.emit('timeout');
|
||||
|
||||
if (remote._connected) {
|
||||
if (remote.trace) {
|
||||
log.info('timeout:', tx.tx_json);
|
||||
}
|
||||
self._resubmit(3, tx);
|
||||
}
|
||||
};
|
||||
|
||||
function submitTransaction() {
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
submitRequest.timeout(self._submissionTimeout, requestTimeout);
|
||||
tx.submissions = submitRequest.broadcast();
|
||||
|
||||
tx.attempts++;
|
||||
tx.emit('postsubmit');
|
||||
};
|
||||
|
||||
tx.submitIndex = this._remote._ledger_current_index;
|
||||
|
||||
if (tx.attempts === 0) {
|
||||
tx.initialSubmitIndex = tx.submitIndex;
|
||||
}
|
||||
|
||||
if (!tx._setLastLedger) {
|
||||
// Honor LastLedgerSequence set by user of API. If
|
||||
// left unset by API, bump LastLedgerSequence
|
||||
tx.tx_json.LastLedgerSequence = tx.submitIndex + 8;
|
||||
}
|
||||
|
||||
tx.lastLedgerSequence = tx.tx_json.LastLedgerSequence;
|
||||
|
||||
if (remote.local_signing) {
|
||||
tx.sign(prepareSubmit);
|
||||
} else {
|
||||
prepareSubmit();
|
||||
}
|
||||
|
||||
return submitRequest;
|
||||
};
|
||||
|
||||
TransactionManager._isNoOp = function(transaction) {
|
||||
return (typeof transaction === 'object')
|
||||
&& (typeof transaction.tx_json === 'object')
|
||||
&& (transaction.tx_json.TransactionType === 'AccountSet')
|
||||
&& (transaction.tx_json.Flags === 0);
|
||||
};
|
||||
|
||||
TransactionManager._isRemoteError = function(error) {
|
||||
return (typeof error === 'object')
|
||||
&& (error.error === 'remoteError')
|
||||
&& (typeof error.remote === 'object');
|
||||
};
|
||||
|
||||
TransactionManager._isNotFound = function(error) {
|
||||
return TransactionManager._isRemoteError(error)
|
||||
&& /^(txnNotFound|transactionNotFound)$/.test(error.remote.error);
|
||||
};
|
||||
|
||||
TransactionManager._isTooBusy = function(error) {
|
||||
return TransactionManager._isRemoteError(error)
|
||||
&& (error.remote.error === 'tooBusy');
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point for TransactionManager submission
|
||||
*
|
||||
* @param {Transaction} tx
|
||||
*/
|
||||
|
||||
TransactionManager.prototype.submit = function(tx) {
|
||||
var self = this;
|
||||
var remote = this._remote;
|
||||
|
||||
// If sequence number is not yet known, defer until it is.
|
||||
if (typeof this._nextSequence !== 'number') {
|
||||
this.once('sequence_loaded', this.submit.bind(this, tx));
|
||||
return;
|
||||
}
|
||||
|
||||
// Finalized (e.g. aborted) transactions must stop all activity
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
function cleanup(message) {
|
||||
// ND: We can just remove this `tx` by identity
|
||||
self._pending.remove(tx);
|
||||
tx.emit('final', message);
|
||||
if (remote.trace) {
|
||||
log.info('transaction finalized:', tx.tx_json, self._pending.getLength());
|
||||
}
|
||||
};
|
||||
|
||||
tx.once('cleanup', cleanup);
|
||||
|
||||
tx.on('save', function() {
|
||||
self.emit('save', tx);
|
||||
});
|
||||
|
||||
tx.once('error', function(message) {
|
||||
tx._errorHandler(message);
|
||||
});
|
||||
|
||||
tx.once('success', function(message) {
|
||||
tx._successHandler(message);
|
||||
});
|
||||
|
||||
tx.once('abort', function() {
|
||||
tx.emit('error', new RippleError('tejAbort', 'Transaction aborted'));
|
||||
});
|
||||
|
||||
if (typeof tx.tx_json.Sequence !== 'number') {
|
||||
tx.tx_json.Sequence = this._nextSequence++;
|
||||
}
|
||||
|
||||
// Attach secret, associate transaction with a server, attach fee.
|
||||
// If the transaction can't complete, decrement sequence so that
|
||||
// subsequent transactions
|
||||
if (!tx.complete()) {
|
||||
this._nextSequence--;
|
||||
return;
|
||||
}
|
||||
|
||||
tx.attempts = 0;
|
||||
tx.submissions = 0;
|
||||
tx.responses = 0;
|
||||
|
||||
// ND: this is the ONLY place we put the tx into the queue. The
|
||||
// TransactionQueue queue is merely a list, so any mutations to tx._hash
|
||||
// will cause subsequent look ups (eg. inside 'transaction-outbound'
|
||||
// validated transaction clearing) to fail.
|
||||
this._pending.push(tx);
|
||||
this._request(tx);
|
||||
};
|
||||
|
||||
exports.TransactionManager = TransactionManager;
|
||||
103
src/js/ripple/transactionqueue.js
Normal file
103
src/js/ripple/transactionqueue.js
Normal file
@@ -0,0 +1,103 @@
|
||||
|
||||
/**
|
||||
* Manager for pending transactions
|
||||
*/
|
||||
|
||||
var LRU = require('lru-cache');
|
||||
var Transaction = require('./transaction').Transaction;
|
||||
|
||||
function TransactionQueue() {
|
||||
this._queue = [ ];
|
||||
this._idCache = LRU();
|
||||
this._sequenceCache = LRU();
|
||||
};
|
||||
|
||||
/**
|
||||
* Store received (validated) sequence
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.addReceivedSequence = function(sequence) {
|
||||
this._sequenceCache.set(String(sequence), true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that sequence number has been consumed by a validated
|
||||
* transaction
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.hasSequence = function(sequence) {
|
||||
return this._sequenceCache.has(String(sequence));
|
||||
};
|
||||
|
||||
/**
|
||||
* Store received (validated) ID transaction
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.addReceivedId = function(id, transaction) {
|
||||
this._idCache.set(id, transaction);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get received (validated) transaction by ID
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.getReceived = function(id) {
|
||||
return this._idCache.get(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a submitted transaction by ID. Transactions
|
||||
* may have multiple associated IDs.
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.getSubmission = function(id) {
|
||||
var result = void(0);
|
||||
|
||||
for (var i=0, tx; (tx=this._queue[i]); i++) {
|
||||
if (~tx.submittedIDs.indexOf(id)) {
|
||||
result = tx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a transaction from the queue
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.remove = function(tx) {
|
||||
// ND: We are just removing the Transaction by identity
|
||||
var i = this._queue.length;
|
||||
|
||||
if (typeof tx === 'string') {
|
||||
tx = this.getSubmission(tx);
|
||||
}
|
||||
|
||||
if (!(tx instanceof Transaction)) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (i--) {
|
||||
if (this._queue[i] === tx) {
|
||||
this._queue.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TransactionQueue.prototype.push = function(tx) {
|
||||
this._queue.push(tx);
|
||||
};
|
||||
|
||||
TransactionQueue.prototype.forEach = function(fn) {
|
||||
this._queue.forEach(fn);
|
||||
};
|
||||
|
||||
TransactionQueue.prototype.length =
|
||||
TransactionQueue.prototype.getLength = function() {
|
||||
return this._queue.length;
|
||||
};
|
||||
|
||||
exports.TransactionQueue = TransactionQueue;
|
||||
@@ -1,31 +1,28 @@
|
||||
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var utils = require('./utils');
|
||||
var sjcl = utils.sjcl;
|
||||
var config = require('./config');
|
||||
var jsbn = require('./jsbn');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var nbi = jsbn.nbi;
|
||||
|
||||
var Base = require('./base').Base;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
//
|
||||
// Abstract UInt class
|
||||
//
|
||||
// Base class for UInt??? classes
|
||||
// Base class for UInt classes
|
||||
//
|
||||
|
||||
var UInt = function () {
|
||||
var UInt = function() {
|
||||
// Internal form: NaN or BigInteger
|
||||
this._value = NaN;
|
||||
|
||||
this._update();
|
||||
};
|
||||
|
||||
UInt.json_rewrite = function (j, opts) {
|
||||
UInt.json_rewrite = function(j, opts) {
|
||||
return this.from_json(j).to_json(opts);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_generic = function (j) {
|
||||
UInt.from_generic = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
@@ -34,7 +31,7 @@ UInt.from_generic = function (j) {
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_hex = function (j) {
|
||||
UInt.from_hex = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
@@ -43,7 +40,7 @@ UInt.from_hex = function (j) {
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_json = function (j) {
|
||||
UInt.from_json = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
@@ -52,7 +49,7 @@ UInt.from_json = function (j) {
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_bits = function (j) {
|
||||
UInt.from_bits = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
@@ -61,7 +58,7 @@ UInt.from_bits = function (j) {
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_bytes = function (j) {
|
||||
UInt.from_bytes = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
@@ -70,7 +67,7 @@ UInt.from_bytes = function (j) {
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_bn = function (j) {
|
||||
UInt.from_bn = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
@@ -78,90 +75,114 @@ UInt.from_bn = function (j) {
|
||||
}
|
||||
};
|
||||
|
||||
UInt.is_valid = function (j) {
|
||||
// Return a new UInt from j.
|
||||
UInt.from_number = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return (new this()).parse_number(j);
|
||||
}
|
||||
};
|
||||
|
||||
UInt.is_valid = function(j) {
|
||||
return this.from_json(j).is_valid();
|
||||
};
|
||||
|
||||
UInt.prototype.clone = function () {
|
||||
UInt.prototype.clone = function() {
|
||||
return this.copyTo(new this.constructor());
|
||||
};
|
||||
|
||||
// Returns copy.
|
||||
UInt.prototype.copyTo = function (d) {
|
||||
UInt.prototype.copyTo = function(d) {
|
||||
d._value = this._value;
|
||||
|
||||
if (typeof d._update === 'function') {
|
||||
d._update();
|
||||
}
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
UInt.prototype.equals = function (d) {
|
||||
UInt.prototype.equals = function(d) {
|
||||
return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value);
|
||||
};
|
||||
|
||||
UInt.prototype.is_valid = function () {
|
||||
UInt.prototype.is_valid = function() {
|
||||
return this._value instanceof BigInteger;
|
||||
};
|
||||
|
||||
UInt.prototype.is_zero = function () {
|
||||
UInt.prototype.is_zero = function() {
|
||||
return this._value.equals(BigInteger.ZERO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update any derivative values.
|
||||
*
|
||||
* This allows subclasses to maintain caches of any data that they derive from
|
||||
* the main _value. For example, the Currency class keeps the currency type, the
|
||||
* currency code and other information about the currency cached.
|
||||
*
|
||||
* The reason for keeping this mechanism in this class is so every subclass can
|
||||
* call it whenever it modifies the internal state.
|
||||
*/
|
||||
UInt.prototype._update = function() {
|
||||
// Nothing to do by default. Subclasses will override this.
|
||||
};
|
||||
|
||||
// value = NaN on error.
|
||||
UInt.prototype.parse_generic = function (j) {
|
||||
UInt.prototype.parse_generic = function(j) {
|
||||
// Canonicalize and validate
|
||||
if (config.accounts && j in config.accounts)
|
||||
if (config.accounts && (j in config.accounts)) {
|
||||
j = config.accounts[j].account;
|
||||
}
|
||||
|
||||
switch (j) {
|
||||
case undefined:
|
||||
case "0":
|
||||
case this.constructor.STR_ZERO:
|
||||
case this.constructor.ACCOUNT_ZERO:
|
||||
case this.constructor.HEX_ZERO:
|
||||
this._value = nbi();
|
||||
break;
|
||||
case undefined:
|
||||
case '0':
|
||||
case this.constructor.STR_ZERO:
|
||||
case this.constructor.ACCOUNT_ZERO:
|
||||
case this.constructor.HEX_ZERO:
|
||||
this._value = BigInteger.valueOf();
|
||||
break;
|
||||
|
||||
case "1":
|
||||
case this.constructor.STR_ONE:
|
||||
case this.constructor.ACCOUNT_ONE:
|
||||
case this.constructor.HEX_ONE:
|
||||
this._value = new BigInteger([1]);
|
||||
case '1':
|
||||
case this.constructor.STR_ONE:
|
||||
case this.constructor.ACCOUNT_ONE:
|
||||
case this.constructor.HEX_ONE:
|
||||
this._value = new BigInteger([1]);
|
||||
break;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
if ('string' !== typeof j) {
|
||||
this._value = NaN;
|
||||
}
|
||||
else if (j[0] === "r") {
|
||||
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
|
||||
}
|
||||
else if (this.constructor.width === j.length) {
|
||||
this._value = new BigInteger(utils.stringToArray(j), 256);
|
||||
}
|
||||
else if ((this.constructor.width*2) === j.length) {
|
||||
// XXX Check char set!
|
||||
this._value = new BigInteger(j, 16);
|
||||
}
|
||||
else {
|
||||
this._value = NaN;
|
||||
}
|
||||
default:
|
||||
if (typeof j !== 'string') {
|
||||
this._value = NaN;
|
||||
} else if (this.constructor.width === j.length) {
|
||||
this._value = new BigInteger(utils.stringToArray(j), 256);
|
||||
} else if ((this.constructor.width * 2) === j.length) {
|
||||
// XXX Check char set!
|
||||
this._value = new BigInteger(j, 16);
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_hex = function (j) {
|
||||
if ('string' === typeof j &&
|
||||
j.length === (this.constructor.width * 2)) {
|
||||
this._value = new BigInteger(j, 16);
|
||||
UInt.prototype.parse_hex = function(j) {
|
||||
if (typeof j === 'string' && j.length === (this.constructor.width * 2)) {
|
||||
this._value = new BigInteger(j, 16);
|
||||
} else {
|
||||
this._value = NaN;
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_bits = function (j) {
|
||||
UInt.prototype.parse_bits = function(j) {
|
||||
if (sjcl.bitArray.bitLength(j) !== this.constructor.width * 8) {
|
||||
this._value = NaN;
|
||||
} else {
|
||||
@@ -169,72 +190,101 @@ UInt.prototype.parse_bits = function (j) {
|
||||
this.parse_bytes(bytes);
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_bytes = function (j) {
|
||||
|
||||
UInt.prototype.parse_bytes = function(j) {
|
||||
if (!Array.isArray(j) || j.length !== this.constructor.width) {
|
||||
this._value = NaN;
|
||||
} else {
|
||||
this._value = new BigInteger(j, 256);
|
||||
this._value = new BigInteger([0].concat(j), 256);
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
UInt.prototype.parse_json = UInt.prototype.parse_hex;
|
||||
|
||||
UInt.prototype.parse_bn = function (j) {
|
||||
if (j instanceof sjcl.bn &&
|
||||
j.bitLength() <= this.constructor.width * 8) {
|
||||
UInt.prototype.parse_bn = function(j) {
|
||||
if ((j instanceof sjcl.bn) && j.bitLength() <= this.constructor.width * 8) {
|
||||
var bytes = sjcl.codec.bytes.fromBits(j.toBits());
|
||||
this._value = new BigInteger(bytes, 256);
|
||||
this._value = new BigInteger(bytes, 256);
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_number = function(j) {
|
||||
this._value = NaN;
|
||||
|
||||
if (typeof j === 'number' && isFinite(j) && j >= 0) {
|
||||
this._value = new BigInteger(String(j));
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Convert from internal form.
|
||||
UInt.prototype.to_bytes = function () {
|
||||
if (!(this._value instanceof BigInteger))
|
||||
UInt.prototype.to_bytes = function() {
|
||||
if (!(this._value instanceof BigInteger)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var bytes = this._value.toByteArray();
|
||||
bytes = bytes.map(function (b) { return (b+256) % 256; });
|
||||
|
||||
bytes = bytes.map(function(b) {
|
||||
return (b + 256) % 256;
|
||||
});
|
||||
|
||||
var target = this.constructor.width;
|
||||
|
||||
// XXX Make sure only trim off leading zeros.
|
||||
bytes = bytes.slice(-target);
|
||||
while (bytes.length < target) bytes.unshift(0);
|
||||
|
||||
while (bytes.length < target) {
|
||||
bytes.unshift(0);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
};
|
||||
|
||||
UInt.prototype.to_hex = function () {
|
||||
if (!(this._value instanceof BigInteger))
|
||||
UInt.prototype.to_hex = function() {
|
||||
if (!(this._value instanceof BigInteger)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var bytes = this.to_bytes();
|
||||
|
||||
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(bytes)).toUpperCase();
|
||||
};
|
||||
|
||||
UInt.prototype.to_json = UInt.prototype.to_hex;
|
||||
|
||||
UInt.prototype.to_bits = function () {
|
||||
if (!(this._value instanceof BigInteger))
|
||||
UInt.prototype.to_bits = function() {
|
||||
if (!(this._value instanceof BigInteger)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var bytes = this.to_bytes();
|
||||
|
||||
return sjcl.codec.bytes.toBits(bytes);
|
||||
};
|
||||
|
||||
UInt.prototype.to_bn = function () {
|
||||
if (!(this._value instanceof BigInteger))
|
||||
UInt.prototype.to_bn = function() {
|
||||
if (!(this._value instanceof BigInteger)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var bits = this.to_bits();
|
||||
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var utils = require('./utils');
|
||||
var config = require('./config');
|
||||
var jsbn = require('./jsbn');
|
||||
var extend = require('extend');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var nbi = jsbn.nbi;
|
||||
|
||||
var UInt = require('./uint').UInt,
|
||||
Base = require('./base').Base;
|
||||
var utils = require('./utils');
|
||||
var extend = require('extend');
|
||||
var UInt = require('./uint').UInt;
|
||||
|
||||
//
|
||||
// UInt128 support
|
||||
@@ -24,9 +15,9 @@ UInt128.width = 16;
|
||||
UInt128.prototype = extend({}, UInt.prototype);
|
||||
UInt128.prototype.constructor = UInt128;
|
||||
|
||||
var HEX_ZERO = UInt128.HEX_ZERO = "00000000000000000000000000000000";
|
||||
var HEX_ONE = UInt128.HEX_ONE = "00000000000000000000000000000000";
|
||||
var STR_ZERO = UInt128.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
var STR_ONE = UInt128.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
var HEX_ZERO = UInt128.HEX_ZERO = '00000000000000000000000000000000';
|
||||
var HEX_ONE = UInt128.HEX_ONE = '00000000000000000000000000000000';
|
||||
var STR_ZERO = UInt128.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
var STR_ONE = UInt128.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
|
||||
exports.UInt128 = UInt128;
|
||||
|
||||
@@ -1,71 +1,103 @@
|
||||
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var utils = require('./utils');
|
||||
var config = require('./config');
|
||||
var jsbn = require('./jsbn');
|
||||
var extend = require('extend');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var nbi = jsbn.nbi;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
var UInt = require('./uint').UInt,
|
||||
Base = require('./base').Base;
|
||||
var UInt = require('./uint').UInt;
|
||||
var Base = require('./base').Base;
|
||||
|
||||
//
|
||||
// UInt160 support
|
||||
//
|
||||
|
||||
var UInt160 = extend(function () {
|
||||
var UInt160 = extend(function() {
|
||||
// Internal form: NaN or BigInteger
|
||||
this._value = NaN;
|
||||
this._version_byte = void(0);
|
||||
this._update();
|
||||
}, UInt);
|
||||
|
||||
UInt160.width = 20;
|
||||
UInt160.prototype = extend({}, UInt.prototype);
|
||||
UInt160.prototype.constructor = UInt160;
|
||||
|
||||
var ACCOUNT_ZERO = UInt160.ACCOUNT_ZERO = "rrrrrrrrrrrrrrrrrrrrrhoLvTp";
|
||||
var ACCOUNT_ONE = UInt160.ACCOUNT_ONE = "rrrrrrrrrrrrrrrrrrrrBZbvji";
|
||||
var HEX_ZERO = UInt160.HEX_ZERO = "0000000000000000000000000000000000000000";
|
||||
var HEX_ONE = UInt160.HEX_ONE = "0000000000000000000000000000000000000001";
|
||||
var STR_ZERO = UInt160.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
var STR_ONE = UInt160.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
var ACCOUNT_ZERO = UInt160.ACCOUNT_ZERO = 'rrrrrrrrrrrrrrrrrrrrrhoLvTp';
|
||||
var ACCOUNT_ONE = UInt160.ACCOUNT_ONE = 'rrrrrrrrrrrrrrrrrrrrBZbvji';
|
||||
var HEX_ZERO = UInt160.HEX_ZERO = '0000000000000000000000000000000000000000';
|
||||
var HEX_ONE = UInt160.HEX_ONE = '0000000000000000000000000000000000000001';
|
||||
var STR_ZERO = UInt160.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
var STR_ONE = UInt160.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
|
||||
UInt160.prototype.set_version = function(j) {
|
||||
this._version_byte = j;
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt160.prototype.get_version = function() {
|
||||
return this._version_byte;
|
||||
};
|
||||
|
||||
// value = NaN on error.
|
||||
UInt160.prototype.parse_json = function (j) {
|
||||
UInt160.prototype.parse_json = function(j) {
|
||||
// Canonicalize and validate
|
||||
if (config.accounts && j in config.accounts)
|
||||
if (config.accounts && (j in config.accounts)) {
|
||||
j = config.accounts[j].account;
|
||||
}
|
||||
|
||||
if ('number' === typeof j) {
|
||||
this._value = new BigInteger(String(j));
|
||||
if (typeof j === 'number' && !isNaN(j)) {
|
||||
// Allow raw numbers - DEPRECATED
|
||||
// This is used mostly by the test suite and is supported
|
||||
// as a legacy feature only. DO NOT RELY ON THIS BEHAVIOR.
|
||||
this._value = new BigInteger(String(j));
|
||||
this._version_byte = Base.VER_ACCOUNT_ID;
|
||||
} else if (typeof j !== 'string') {
|
||||
this._value = NaN;
|
||||
} else if (j[0] === 'r') {
|
||||
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
|
||||
this._version_byte = Base.VER_ACCOUNT_ID;
|
||||
} else {
|
||||
this.parse_hex(j);
|
||||
}
|
||||
else if ('string' !== typeof j) {
|
||||
this._value = NaN;
|
||||
}
|
||||
else if (j[0] === "r") {
|
||||
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
|
||||
}
|
||||
else {
|
||||
this._value = NaN;
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt160.prototype.parse_generic = function(j) {
|
||||
UInt.prototype.parse_generic.call(this, j);
|
||||
|
||||
if (isNaN(this._value)) {
|
||||
if ((typeof j === 'string') && j[0] === 'r') {
|
||||
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
|
||||
}
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// XXX Json form should allow 0 and 1, C++ doesn't currently allow it.
|
||||
UInt160.prototype.to_json = function (opts) {
|
||||
UInt160.prototype.to_json = function(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
if (!(this._value instanceof BigInteger))
|
||||
return NaN;
|
||||
if (this._value instanceof BigInteger) {
|
||||
// If this value has a type, return a Base58 encoded string.
|
||||
if (typeof this._version_byte === 'number') {
|
||||
var output = Base.encode_check(this._version_byte, this.to_bytes());
|
||||
|
||||
var output = Base.encode_check(Base.VER_ACCOUNT_ID, this.to_bytes());
|
||||
if (opts.gateways && output in opts.gateways) {
|
||||
output = opts.gateways[output];
|
||||
}
|
||||
|
||||
if (opts.gateways && output in opts.gateways)
|
||||
output = opts.gateways[output];
|
||||
|
||||
return output;
|
||||
return output;
|
||||
} else {
|
||||
return this.to_hex();
|
||||
}
|
||||
}
|
||||
return NaN;
|
||||
};
|
||||
|
||||
exports.UInt160 = UInt160;
|
||||
|
||||
@@ -1,34 +1,23 @@
|
||||
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var utils = require('./utils');
|
||||
var config = require('./config');
|
||||
var jsbn = require('./jsbn');
|
||||
var extend = require('extend');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var nbi = jsbn.nbi;
|
||||
|
||||
var UInt = require('./uint').UInt,
|
||||
Base = require('./base').Base;
|
||||
var utils = require('./utils');
|
||||
var extend = require('extend');
|
||||
var UInt = require('./uint').UInt;
|
||||
|
||||
//
|
||||
// UInt256 support
|
||||
//
|
||||
|
||||
var UInt256 = extend(function () {
|
||||
var UInt256 = extend(function() {
|
||||
// Internal form: NaN or BigInteger
|
||||
this._value = NaN;
|
||||
this._value = NaN;
|
||||
}, UInt);
|
||||
|
||||
UInt256.width = 32;
|
||||
UInt256.prototype = extend({}, UInt.prototype);
|
||||
UInt256.prototype.constructor = UInt256;
|
||||
|
||||
var HEX_ZERO = UInt256.HEX_ZERO = "00000000000000000000000000000000" +
|
||||
"00000000000000000000000000000000";
|
||||
var HEX_ONE = UInt256.HEX_ONE = "00000000000000000000000000000000" +
|
||||
"00000000000000000000000000000001";
|
||||
var STR_ZERO = UInt256.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
var STR_ONE = UInt256.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
var HEX_ZERO = UInt256.HEX_ZERO = '00000000000000000000000000000000' + '00000000000000000000000000000000';
|
||||
var HEX_ONE = UInt256.HEX_ONE = '00000000000000000000000000000000' + '00000000000000000000000000000001';
|
||||
var STR_ZERO = UInt256.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
var STR_ONE = UInt256.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
|
||||
exports.UInt256 = UInt256;
|
||||
|
||||
@@ -1,114 +1,113 @@
|
||||
Function.prototype.method = function(name,func) {
|
||||
this.prototype[name] = func;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
var filterErr = function(code, done) {
|
||||
function filterErr(code, done) {
|
||||
return function(e) {
|
||||
done(e.code !== code ? e : undefined);
|
||||
};
|
||||
done(e.code !== code ? e : void(0));
|
||||
};
|
||||
};
|
||||
|
||||
var throwErr = function(done) {
|
||||
function throwErr(done) {
|
||||
return function(e) {
|
||||
if (e)
|
||||
throw e;
|
||||
|
||||
done();
|
||||
};
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
done();
|
||||
};
|
||||
};
|
||||
|
||||
var trace = function(comment, func) {
|
||||
|
||||
function trace(comment, func) {
|
||||
return function() {
|
||||
console.log("%s: %s", trace, arguments.toString);
|
||||
func(arguments);
|
||||
};
|
||||
console.log('%s: %s', trace, arguments.toString);
|
||||
func(arguments);
|
||||
};
|
||||
};
|
||||
|
||||
var arraySet = function (count, value) {
|
||||
var i, a = new Array(count);
|
||||
function arraySet(count, value) {
|
||||
var a = new Array(count);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
for (var i=0; i<count; i++) {
|
||||
a[i] = value;
|
||||
}
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
var hexToString = function (h) {
|
||||
var a = [];
|
||||
var i = 0;
|
||||
function hexToString(h) {
|
||||
var a = [];
|
||||
var i = 0;
|
||||
|
||||
if (h.length % 2) {
|
||||
a.push(String.fromCharCode(parseInt(h.substring(0, 1), 16)));
|
||||
i = 1;
|
||||
}
|
||||
|
||||
for (; i != h.length; i += 2) {
|
||||
for (; i<h.length; i+=2) {
|
||||
a.push(String.fromCharCode(parseInt(h.substring(i, i+2), 16)));
|
||||
}
|
||||
|
||||
return a.join("");
|
||||
|
||||
return a.join('');
|
||||
};
|
||||
|
||||
var stringToHex = function (s) {
|
||||
return Array.prototype.map.call(s, function (c) {
|
||||
var b = c.charCodeAt(0);
|
||||
|
||||
return b < 16 ? "0" + b.toString(16) : b.toString(16);
|
||||
}).join("");
|
||||
function stringToHex(s) {
|
||||
var result = '';
|
||||
for (var i=0; i<s.length; i++) {
|
||||
var b = s.charCodeAt(i);
|
||||
result += b < 16 ? '0' + b.toString(16) : b.toString(16);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
var stringToArray = function (s) {
|
||||
function stringToArray(s) {
|
||||
var a = new Array(s.length);
|
||||
var i;
|
||||
|
||||
for (i = 0; i != a.length; i += 1)
|
||||
for (var i=0; i<a.length; i+=1) {
|
||||
a[i] = s.charCodeAt(i);
|
||||
}
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
var hexToArray = function (h) {
|
||||
function hexToArray(h) {
|
||||
return stringToArray(hexToString(h));
|
||||
}
|
||||
};
|
||||
|
||||
var chunkString = function (str, n, leftAlign) {
|
||||
function chunkString(str, n, leftAlign) {
|
||||
var ret = [];
|
||||
var i=0, len=str.length;
|
||||
|
||||
if (leftAlign) {
|
||||
i = str.length % n;
|
||||
if (i) ret.push(str.slice(0, i));
|
||||
if (i) {
|
||||
ret.push(str.slice(0, i));
|
||||
}
|
||||
}
|
||||
for(; i < len; i += n) {
|
||||
ret.push(str.slice(i, n+i));
|
||||
|
||||
for(; i<len; i+=n) {
|
||||
ret.push(str.slice(i, n + i));
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
var logObject = function (msg, obj) {
|
||||
console.log(msg, JSON.stringify(obj, undefined, 2));
|
||||
};
|
||||
|
||||
var assert = function (assertion, msg) {
|
||||
function assert(assertion, msg) {
|
||||
if (!assertion) {
|
||||
throw new Error("Assertion failed" + (msg ? ": "+msg : "."));
|
||||
throw new Error('Assertion failed' + (msg ? ': ' + msg : '.'));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return unique values in array.
|
||||
*/
|
||||
var arrayUnique = function (arr) {
|
||||
function arrayUnique(arr) {
|
||||
var u = {}, a = [];
|
||||
for (var i = 0, l = arr.length; i < l; ++i){
|
||||
if (u.hasOwnProperty(arr[i])) {
|
||||
|
||||
for (var i=0, l=arr.length; i<l; i++){
|
||||
var k = arr[i];
|
||||
if (u[k]) {
|
||||
continue;
|
||||
}
|
||||
a.push(arr[i]);
|
||||
u[arr[i]] = 1;
|
||||
a.push(k);
|
||||
u[k] = true;
|
||||
}
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
@@ -117,10 +116,28 @@ var arrayUnique = function (arr) {
|
||||
*
|
||||
* JavaScript timestamps are unix epoch in milliseconds.
|
||||
*/
|
||||
var toTimestamp = function (rpepoch) {
|
||||
function toTimestamp(rpepoch) {
|
||||
return (rpepoch + 0x386D4380) * 1000;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a JavaScript timestamp or Date to a Ripple epoch.
|
||||
*
|
||||
* JavaScript timestamps are unix epoch in milliseconds.
|
||||
*/
|
||||
function fromTimestamp(rpepoch) {
|
||||
if (rpepoch instanceof Date) {
|
||||
rpepoch = rpepoch.getTime();
|
||||
}
|
||||
|
||||
return Math.round(rpepoch / 1000) - 0x386D4380;
|
||||
};
|
||||
|
||||
exports.time = {
|
||||
fromRipple: toTimestamp,
|
||||
toRipple: fromTimestamp
|
||||
};
|
||||
|
||||
exports.trace = trace;
|
||||
exports.arraySet = arraySet;
|
||||
exports.hexToString = hexToString;
|
||||
@@ -128,9 +145,14 @@ exports.hexToArray = hexToArray;
|
||||
exports.stringToArray = stringToArray;
|
||||
exports.stringToHex = stringToHex;
|
||||
exports.chunkString = chunkString;
|
||||
exports.logObject = logObject;
|
||||
exports.assert = assert;
|
||||
exports.arrayUnique = arrayUnique;
|
||||
exports.toTimestamp = toTimestamp;
|
||||
exports.fromTimestamp = fromTimestamp;
|
||||
|
||||
// Going up three levels is needed to escape the src-cov folder used for the
|
||||
// test coverage stuff.
|
||||
exports.sjcl = require('../../../build/sjcl');
|
||||
exports.jsbn = require('../../../src/js/jsbn/jsbn');
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
var exports = module.exports = require('./utils.js');
|
||||
|
||||
// We override this function for browsers, because they print objects nicer
|
||||
// natively than JSON.stringify can.
|
||||
exports.logObject = function (msg, obj) {
|
||||
if (/MSIE/.test(navigator.userAgent)) {
|
||||
console.log(msg, JSON.stringify(obj));
|
||||
} else {
|
||||
console.log(msg, "", obj);
|
||||
}
|
||||
};
|
||||
593
src/js/ripple/vaultclient.js
Normal file
593
src/js/ripple/vaultclient.js
Normal file
@@ -0,0 +1,593 @@
|
||||
var async = require('async');
|
||||
var blobClient = require('./blob').BlobClient;
|
||||
var AuthInfo = require('./authinfo').AuthInfo;
|
||||
var crypt = require('./crypt').Crypt;
|
||||
var log = require('./log').sub('vault');
|
||||
function VaultClient(opts) {
|
||||
|
||||
var self = this;
|
||||
|
||||
if (!opts) {
|
||||
opts = { };
|
||||
}
|
||||
|
||||
if (typeof opts === 'string') {
|
||||
opts = { domain: opts };
|
||||
}
|
||||
|
||||
this.domain = opts.domain || 'ripple.com';
|
||||
this.infos = { };
|
||||
};
|
||||
|
||||
/**
|
||||
* getAuthInfo
|
||||
* gets auth info for a username. returns authinfo
|
||||
* even if user does not exists (with exist set to false)
|
||||
* @param {string} username
|
||||
* @param {function} callback
|
||||
*/
|
||||
VaultClient.prototype.getAuthInfo = function (username, callback) {
|
||||
|
||||
AuthInfo.get(this.domain, username, function(err, authInfo) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (authInfo.version !== 3) {
|
||||
return callback(new Error('This wallet is incompatible with this version of the vault-client.'));
|
||||
}
|
||||
|
||||
if (!authInfo.pakdf) {
|
||||
return callback(new Error('No settings for PAKDF in auth packet.'));
|
||||
}
|
||||
|
||||
if (typeof authInfo.blobvault !== 'string') {
|
||||
return callback(new Error('No blobvault specified in the authinfo.'));
|
||||
}
|
||||
|
||||
callback(null, authInfo);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* _deriveLoginKeys
|
||||
* method designed for asnyc waterfall
|
||||
*/
|
||||
|
||||
VaultClient.prototype._deriveLoginKeys = function (authInfo, password, callback) {
|
||||
var normalizedUsername = authInfo.username.toLowerCase().replace(/-/g, '');
|
||||
|
||||
//derive login keys
|
||||
crypt.derive(authInfo.pakdf, 'login', normalizedUsername, password, function(err, keys) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, authInfo, password, keys);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* _deriveUnlockKey
|
||||
* method designed for asnyc waterfall
|
||||
*/
|
||||
|
||||
VaultClient.prototype._deriveUnlockKey = function (authInfo, password, keys, callback) {
|
||||
var normalizedUsername = authInfo.username.toLowerCase().replace(/-/g, '');
|
||||
|
||||
//derive unlock key
|
||||
crypt.derive(authInfo.pakdf, 'unlock', normalizedUsername, password, function(err, unlock) {
|
||||
if (err) {
|
||||
log.error('derive:', err);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!keys) {
|
||||
keys = { };
|
||||
}
|
||||
|
||||
keys.unlock = unlock.unlock;
|
||||
callback(null, authInfo, keys);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a ripple name from a given account address, if it has one
|
||||
* @param {string} address - Account address to query
|
||||
* @param {string} url - Url of blob vault
|
||||
*/
|
||||
|
||||
VaultClient.prototype.getRippleName = function(address, url, callback) {
|
||||
//use the url from previously retrieved authInfo, if necessary
|
||||
if (!url) {
|
||||
callback(new Error('Blob vault URL is required'));
|
||||
} else {
|
||||
blobClient.getRippleName(url, address, callback);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check blobvault for existance of username
|
||||
*
|
||||
* @param {string} username
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
|
||||
VaultClient.prototype.exists = function(username, callback) {
|
||||
AuthInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, !!authInfo.exists);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Authenticate and retrieve a decrypted blob using a ripple name and password
|
||||
*
|
||||
* @param {string} username
|
||||
* @param {string} password
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
|
||||
VaultClient.prototype.login = function(username, password, device_id, callback) {
|
||||
var self = this;
|
||||
|
||||
var steps = [
|
||||
getAuthInfo,
|
||||
self._deriveLoginKeys,
|
||||
getBlob
|
||||
];
|
||||
|
||||
async.waterfall(steps, callback);
|
||||
|
||||
function getAuthInfo(callback) {
|
||||
self.getAuthInfo(username, function(err, authInfo){
|
||||
|
||||
if (authInfo && !authInfo.exists) {
|
||||
return callback(new Error('User does not exist.'));
|
||||
}
|
||||
|
||||
return callback (err, authInfo, password);
|
||||
});
|
||||
}
|
||||
|
||||
function getBlob(authInfo, password, keys, callback) {
|
||||
var options = {
|
||||
url : authInfo.blobvault,
|
||||
blob_id : keys.id,
|
||||
key : keys.crypt,
|
||||
device_id : device_id
|
||||
};
|
||||
|
||||
blobClient.get(options, function(err, blob) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
//save for relogin
|
||||
self.infos[keys.id] = authInfo;
|
||||
|
||||
//migrate missing fields
|
||||
if (blob.missing_fields) {
|
||||
if (blob.missing_fields.encrypted_blobdecrypt_key) {
|
||||
log.info('migration: saving encrypted blob decrypt key');
|
||||
authInfo.blob = blob;
|
||||
//get the key to unlock the secret, then update the blob keys
|
||||
self._deriveUnlockKey(authInfo, password, keys, updateKeys);
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, {
|
||||
blob : blob,
|
||||
username : authInfo.username,
|
||||
verified : authInfo.emailVerified
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function updateKeys (err, params, keys) {
|
||||
if (err || !keys.unlock) {
|
||||
return; //unable to unlock
|
||||
}
|
||||
|
||||
var secret;
|
||||
try {
|
||||
secret = crypt.decrypt(keys.unlock, params.blob.encrypted_secret);
|
||||
} catch (error) {
|
||||
return log.error('decrypt:', error);
|
||||
}
|
||||
|
||||
options = {
|
||||
username : params.username,
|
||||
blob : params.blob,
|
||||
masterkey : secret,
|
||||
keys : keys
|
||||
};
|
||||
|
||||
blobClient.updateKeys(options, function(err, resp){
|
||||
if (err) {
|
||||
log.error('updateKeys:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreive and decrypt blob using a blob url, id and crypt derived previously.
|
||||
*
|
||||
* @param {string} url - Blob vault url
|
||||
* @param {string} id - Blob id from previously retreived blob
|
||||
* @param {string} key - Blob decryption key
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
|
||||
VaultClient.prototype.relogin = function(url, id, key, device_id, callback) {
|
||||
//use the url from previously retrieved authInfo, if necessary
|
||||
if (!url && this.infos[id]) {
|
||||
url = this.infos[id].blobvault;
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
return callback(new Error('Blob vault URL is required'));
|
||||
}
|
||||
|
||||
var options = {
|
||||
url : url,
|
||||
blob_id : id,
|
||||
key : key,
|
||||
device_id : device_id
|
||||
};
|
||||
|
||||
blobClient.get(options, function(err, blob) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback (null, { blob: blob });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt the secret key using a username and password
|
||||
*
|
||||
* @param {string} username
|
||||
* @param {string} password
|
||||
* @param {string} encryptSecret
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
|
||||
VaultClient.prototype.unlock = function(username, password, encryptSecret, fn) {
|
||||
var self = this;
|
||||
|
||||
var steps = [
|
||||
getAuthInfo,
|
||||
self._deriveUnlockKey,
|
||||
unlockSecret
|
||||
];
|
||||
|
||||
async.waterfall(steps, fn);
|
||||
|
||||
function getAuthInfo(callback) {
|
||||
self.getAuthInfo(username, function(err, authInfo){
|
||||
|
||||
if (authInfo && !authInfo.exists) {
|
||||
return callback(new Error('User does not exist.'));
|
||||
}
|
||||
|
||||
return callback (err, authInfo, password, {});
|
||||
});
|
||||
}
|
||||
|
||||
function unlockSecret (authinfo, keys, callback) {
|
||||
|
||||
var secret;
|
||||
try {
|
||||
secret = crypt.decrypt(keys.unlock, encryptSecret);
|
||||
} catch (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
callback(null, {
|
||||
keys : keys,
|
||||
secret : secret
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the decrypted blob and secret key in one step using
|
||||
* the username and password
|
||||
*
|
||||
* @param {string} username
|
||||
* @param {string} password
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
|
||||
VaultClient.prototype.loginAndUnlock = function(username, password, device_id, fn) {
|
||||
var self = this;
|
||||
|
||||
var steps = [
|
||||
login,
|
||||
deriveUnlockKey,
|
||||
unlockSecret
|
||||
];
|
||||
|
||||
async.waterfall(steps, fn);
|
||||
|
||||
function login (callback) {
|
||||
self.login(username, password, device_id, function(err, resp) {
|
||||
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!resp.blob || !resp.blob.encrypted_secret) {
|
||||
return callback(new Error('Unable to retrieve blob and secret.'));
|
||||
}
|
||||
|
||||
if (!resp.blob.id || !resp.blob.key) {
|
||||
return callback(new Error('Unable to retrieve keys.'));
|
||||
}
|
||||
|
||||
//get authInfo via id - would have been saved from login
|
||||
var authInfo = self.infos[resp.blob.id];
|
||||
|
||||
if (!authInfo) {
|
||||
return callback(new Error('Unable to find authInfo'));
|
||||
}
|
||||
|
||||
callback(null, authInfo, password, resp.blob);
|
||||
});
|
||||
};
|
||||
|
||||
function deriveUnlockKey (authInfo, password, blob, callback) {
|
||||
self._deriveUnlockKey(authInfo, password, null, function(err, authInfo, keys){
|
||||
callback(err, keys.unlock, authInfo, blob);
|
||||
});
|
||||
};
|
||||
|
||||
function unlockSecret (unlock, authInfo, blob, callback) {
|
||||
var secret;
|
||||
try {
|
||||
secret = crypt.decrypt(unlock, blob.encrypted_secret);
|
||||
} catch (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
callback(null, {
|
||||
blob : blob,
|
||||
unlock : unlock,
|
||||
secret : secret,
|
||||
username : authInfo.username,
|
||||
verified : authInfo.emailVerified
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify an email address for an existing user
|
||||
*
|
||||
* @param {string} username
|
||||
* @param {string} token - Verification token
|
||||
* @param {function} fn - Callback function
|
||||
*/
|
||||
|
||||
VaultClient.prototype.verify = function(username, token, callback) {
|
||||
var self = this;
|
||||
|
||||
self.getAuthInfo(username, function (err, authInfo){
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
blobClient.verify(authInfo.blobvault, username.toLowerCase(), token, callback);
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* changePassword
|
||||
* @param {object} options
|
||||
* @param {string} options.username
|
||||
* @param {string} options.password
|
||||
* @param {string} options.masterkey
|
||||
* @param {object} options.blob
|
||||
*/
|
||||
|
||||
VaultClient.prototype.changePassword = function (options, fn) {
|
||||
var self = this;
|
||||
var password = String(options.password).trim();
|
||||
|
||||
var steps = [
|
||||
getAuthInfo,
|
||||
self._deriveLoginKeys,
|
||||
self._deriveUnlockKey,
|
||||
changePassword
|
||||
];
|
||||
|
||||
async.waterfall(steps, fn);
|
||||
|
||||
function getAuthInfo(callback) {
|
||||
self.getAuthInfo(options.username, function(err, authInfo) {
|
||||
return callback (err, authInfo, password);
|
||||
});
|
||||
};
|
||||
|
||||
function changePassword (authInfo, keys, callback) {
|
||||
options.keys = keys;
|
||||
blobClient.updateKeys(options, callback);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* rename
|
||||
* rename a ripple account
|
||||
* @param {object} options
|
||||
* @param {string} options.username
|
||||
* @param {string} options.new_username
|
||||
* @param {string} options.password
|
||||
* @param {string} options.masterkey
|
||||
* @param {object} options.blob
|
||||
* @param {function} fn
|
||||
*/
|
||||
|
||||
VaultClient.prototype.rename = function (options, fn) {
|
||||
var self = this;
|
||||
var new_username = String(options.new_username).trim();
|
||||
var password = String(options.password).trim();
|
||||
|
||||
var steps = [
|
||||
getAuthInfo,
|
||||
self._deriveLoginKeys,
|
||||
self._deriveUnlockKey,
|
||||
renameBlob
|
||||
];
|
||||
|
||||
async.waterfall(steps, fn);
|
||||
|
||||
function getAuthInfo(callback) {
|
||||
self.getAuthInfo(new_username, function(err, authInfo){
|
||||
|
||||
if (authInfo && authInfo.exists) {
|
||||
return callback(new Error('username already taken.'));
|
||||
} else {
|
||||
authInfo.username = new_username;
|
||||
}
|
||||
|
||||
return callback (err, authInfo, password);
|
||||
});
|
||||
};
|
||||
|
||||
function renameBlob (authInfo, keys, callback) {
|
||||
options.keys = keys;
|
||||
blobClient.rename(options, callback);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a new user and save to the blob vault
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {string} options.username
|
||||
* @param {string} options.password
|
||||
* @param {string} options.masterkey //optional, will create if absent
|
||||
* @param {string} options.email
|
||||
* @param {string} options.activateLink
|
||||
* @param {object} options.oldUserBlob //optional
|
||||
* @param {function} fn
|
||||
*/
|
||||
|
||||
VaultClient.prototype.register = function(options, fn) {
|
||||
var self = this;
|
||||
var username = String(options.username).trim();
|
||||
var password = String(options.password).trim();
|
||||
var result = self.validateUsername(username);
|
||||
|
||||
if (!result.valid) {
|
||||
return fn(new Error('invalid username.'));
|
||||
}
|
||||
|
||||
var steps = [
|
||||
getAuthInfo,
|
||||
self._deriveLoginKeys,
|
||||
self._deriveUnlockKey,
|
||||
create
|
||||
];
|
||||
|
||||
async.waterfall(steps, fn);
|
||||
|
||||
function getAuthInfo(callback) {
|
||||
self.getAuthInfo(username, function(err, authInfo){
|
||||
return callback (err, authInfo, password);
|
||||
});
|
||||
};
|
||||
|
||||
function create(authInfo, keys, callback) {
|
||||
var params = {
|
||||
url : authInfo.blobvault,
|
||||
id : keys.id,
|
||||
crypt : keys.crypt,
|
||||
unlock : keys.unlock,
|
||||
username : username,
|
||||
email : options.email,
|
||||
masterkey : options.masterkey || crypt.createMaster(),
|
||||
activateLink : options.activateLink,
|
||||
oldUserBlob : options.oldUserBlob,
|
||||
domain : options.domain
|
||||
};
|
||||
|
||||
blobClient.create(params, function(err, blob) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, {
|
||||
blob : blob,
|
||||
username : username
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* validateUsername
|
||||
* check username for validity
|
||||
*/
|
||||
|
||||
VaultClient.prototype.validateUsername = function (username) {
|
||||
username = String(username).trim();
|
||||
var result = {
|
||||
valid : false,
|
||||
reason : ''
|
||||
};
|
||||
|
||||
if (username.length < 2) {
|
||||
result.reason = 'tooshort';
|
||||
} else if (username.length > 20) {
|
||||
result.reason = 'toolong';
|
||||
} else if (!/^[a-zA-Z0-9\-]+$/.exec(username)) {
|
||||
result.reason = 'charset';
|
||||
} else if (/^-/.exec(username)) {
|
||||
result.reason = 'starthyphen';
|
||||
} else if (/-$/.exec(username)) {
|
||||
result.reason = 'endhyphen';
|
||||
} else if (/--/.exec(username)) {
|
||||
result.reason = 'multhyphen';
|
||||
} else {
|
||||
result.valid = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* generateDeviceID
|
||||
* create a new random device ID for 2FA
|
||||
*/
|
||||
VaultClient.prototype.generateDeviceID = function () {
|
||||
return crypt.createSecret(4);
|
||||
};
|
||||
|
||||
/*** pass thru some blob client function ***/
|
||||
|
||||
VaultClient.prototype.resendEmail = blobClient.resendEmail;
|
||||
|
||||
VaultClient.prototype.recoverBlob = blobClient.recoverBlob;
|
||||
|
||||
VaultClient.prototype.deleteBlob = blobClient.deleteBlob;
|
||||
|
||||
VaultClient.prototype.requestToken = blobClient.requestToken;
|
||||
|
||||
VaultClient.prototype.verifyToken = blobClient.verifyToken;
|
||||
|
||||
VaultClient.prototype.getAttestation = blobClient.getAttestation;
|
||||
|
||||
VaultClient.prototype.updateAttestation = blobClient.updateAttestation;
|
||||
|
||||
VaultClient.prototype.getAttestationSummary = blobClient.getAttestationSummary;
|
||||
|
||||
//export by name
|
||||
exports.VaultClient = VaultClient;
|
||||
8
src/js/ripple/wallet.js
Normal file
8
src/js/ripple/wallet.js
Normal file
@@ -0,0 +1,8 @@
|
||||
var sjcl = require('./utils').sjcl;
|
||||
|
||||
var WalletGenerator = require('ripple-wallet-generator')({
|
||||
sjcl: sjcl
|
||||
});
|
||||
|
||||
module.exports = WalletGenerator;
|
||||
|
||||
83
src/js/sjcl-custom/sjcl-ecc-pointextras.js
Normal file
83
src/js/sjcl-custom/sjcl-ecc-pointextras.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Check that the point is valid based on the method described in
|
||||
* SEC 1: Elliptic Curve Cryptography, section 3.2.2.1:
|
||||
* Elliptic Curve Public Key Validation Primitive
|
||||
* http://www.secg.org/download/aid-780/sec1-v2.pdf
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
sjcl.ecc.point.prototype.isValidPoint = function() {
|
||||
|
||||
var self = this;
|
||||
|
||||
var field_modulus = self.curve.field.modulus;
|
||||
|
||||
if (self.isIdentity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that coordinatres are in bounds
|
||||
// Return false if x < 1 or x > (field_modulus - 1)
|
||||
if (((new sjcl.bn(1).greaterEquals(self.x)) &&
|
||||
!self.x.equals(1)) ||
|
||||
(self.x.greaterEquals(field_modulus.sub(1))) &&
|
||||
!self.x.equals(1)) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return false if y < 1 or y > (field_modulus - 1)
|
||||
if (((new sjcl.bn(1).greaterEquals(self.y)) &&
|
||||
!self.y.equals(1)) ||
|
||||
(self.y.greaterEquals(field_modulus.sub(1))) &&
|
||||
!self.y.equals(1)) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!self.isOnCurve()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO check to make sure point is a scalar multiple of base_point
|
||||
|
||||
return true;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that the point is on the curve
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
sjcl.ecc.point.prototype.isOnCurve = function() {
|
||||
|
||||
var self = this;
|
||||
|
||||
var field_order = self.curve.r;
|
||||
var component_a = self.curve.a;
|
||||
var component_b = self.curve.b;
|
||||
var field_modulus = self.curve.field.modulus;
|
||||
|
||||
var left_hand_side = self.y.mul(self.y).mod(field_modulus);
|
||||
var right_hand_side = self.x.mul(self.x).mul(self.x).add(component_a.mul(self.x)).add(component_b).mod(field_modulus);
|
||||
|
||||
return left_hand_side.equals(right_hand_side);
|
||||
|
||||
};
|
||||
|
||||
|
||||
sjcl.ecc.point.prototype.toString = function() {
|
||||
return '(' +
|
||||
this.x.toString() + ', ' +
|
||||
this.y.toString() +
|
||||
')';
|
||||
};
|
||||
|
||||
sjcl.ecc.pointJac.prototype.toString = function() {
|
||||
return '(' +
|
||||
this.x.toString() + ', ' +
|
||||
this.y.toString() + ', ' +
|
||||
this.z.toString() +
|
||||
')';
|
||||
};
|
||||
17
src/js/sjcl-custom/sjcl-ecdsa-canonical.js
Normal file
17
src/js/sjcl-custom/sjcl-ecdsa-canonical.js
Normal file
@@ -0,0 +1,17 @@
|
||||
sjcl.ecc.ecdsa.secretKey.prototype.canonicalizeSignature = function(rs) {
|
||||
var w = sjcl.bitArray,
|
||||
R = this._curve.r,
|
||||
l = R.bitLength();
|
||||
|
||||
var r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)),
|
||||
s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l));
|
||||
|
||||
// For a canonical signature we want the lower of two possible values for s
|
||||
// 0 < s <= n/2
|
||||
if (!R.copy().halveM().greaterEquals(s)) {
|
||||
s = R.sub(s);
|
||||
}
|
||||
|
||||
return w.concat(r.toBits(l), s.toBits(l));
|
||||
};
|
||||
|
||||
306
src/js/sjcl-custom/sjcl-ecdsa-recoverablepublickey.js
Normal file
306
src/js/sjcl-custom/sjcl-ecdsa-recoverablepublickey.js
Normal file
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* This module uses the public key recovery method
|
||||
* described in SEC 1: Elliptic Curve Cryptography,
|
||||
* section 4.1.6, "Public Key Recovery Operation".
|
||||
* http://www.secg.org/download/aid-780/sec1-v2.pdf
|
||||
*
|
||||
* Implementation based on:
|
||||
* https://github.com/bitcoinjs/bitcoinjs-lib/blob/89cf731ac7309b4f98994e3b4b67b7226020181f/src/ecdsa.js
|
||||
*/
|
||||
|
||||
// Defined here so that this value only needs to be calculated once
|
||||
var FIELD_MODULUS_PLUS_ONE_DIVIDED_BY_FOUR;
|
||||
|
||||
/**
|
||||
* Sign the given hash such that the public key, prepending an extra byte
|
||||
* so that the public key will be recoverable from the signature
|
||||
*
|
||||
* @param {bitArray} hash
|
||||
* @param {Number} paranoia
|
||||
* @returns {bitArray} Signature formatted as bitArray
|
||||
*/
|
||||
sjcl.ecc.ecdsa.secretKey.prototype.signWithRecoverablePublicKey = function(hash, paranoia, k_for_testing) {
|
||||
|
||||
var self = this;
|
||||
|
||||
// Convert hash to bits and determine encoding for output
|
||||
var hash_bits;
|
||||
if (typeof hash === 'object' && hash.length > 0 && typeof hash[0] === 'number') {
|
||||
hash_bits = hash;
|
||||
} else {
|
||||
throw new sjcl.exception.invalid('hash. Must be a bitArray');
|
||||
}
|
||||
|
||||
// Sign hash with standard, canonicalized method
|
||||
var standard_signature = self.sign(hash_bits, paranoia, k_for_testing);
|
||||
var canonical_signature = self.canonicalizeSignature(standard_signature);
|
||||
|
||||
// Extract r and s signature components from canonical signature
|
||||
var r_and_s = getRandSFromSignature(self._curve, canonical_signature);
|
||||
|
||||
// Rederive public key
|
||||
var public_key = self._curve.G.mult(sjcl.bn.fromBits(self.get()));
|
||||
|
||||
// Determine recovery factor based on which possible value
|
||||
// returns the correct public key
|
||||
var recovery_factor = calculateRecoveryFactor(self._curve, r_and_s.r, r_and_s.s, hash_bits, public_key);
|
||||
|
||||
// Prepend recovery_factor to signature and encode in DER
|
||||
// The value_to_prepend should be 4 bytes total
|
||||
var value_to_prepend = recovery_factor + 27;
|
||||
|
||||
var final_signature_bits = sjcl.bitArray.concat([value_to_prepend], canonical_signature);
|
||||
|
||||
// Return value in bits
|
||||
return final_signature_bits;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Recover the public key from a signature created with the
|
||||
* signWithRecoverablePublicKey method in this module
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {bitArray} hash
|
||||
* @param {bitArray} signature
|
||||
* @param {sjcl.ecc.curve} [sjcl.ecc.curves['c256']] curve
|
||||
* @returns {sjcl.ecc.ecdsa.publicKey} Public key
|
||||
*/
|
||||
sjcl.ecc.ecdsa.publicKey.recoverFromSignature = function(hash, signature, curve) {
|
||||
|
||||
if (!signature || signature instanceof sjcl.ecc.curve) {
|
||||
throw new sjcl.exception.invalid('must supply hash and signature to recover public key');
|
||||
}
|
||||
|
||||
if (!curve) {
|
||||
curve = sjcl.ecc.curves['c256'];
|
||||
}
|
||||
|
||||
// Convert hash to bits and determine encoding for output
|
||||
var hash_bits;
|
||||
if (typeof hash === 'object' && hash.length > 0 && typeof hash[0] === 'number') {
|
||||
hash_bits = hash;
|
||||
} else {
|
||||
throw new sjcl.exception.invalid('hash. Must be a bitArray');
|
||||
}
|
||||
|
||||
var signature_bits;
|
||||
if (typeof signature === 'object' && signature.length > 0 && typeof signature[0] === 'number') {
|
||||
signature_bits = signature;
|
||||
} else {
|
||||
throw new sjcl.exception.invalid('signature. Must be a bitArray');
|
||||
}
|
||||
|
||||
// Extract recovery_factor from first 4 bytes
|
||||
var recovery_factor = signature_bits[0] - 27;
|
||||
|
||||
if (recovery_factor < 0 || recovery_factor > 3) {
|
||||
throw new sjcl.exception.invalid('signature. Signature must be generated with algorithm ' +
|
||||
'that prepends the recovery factor in order to recover the public key');
|
||||
}
|
||||
|
||||
// Separate r and s values
|
||||
var r_and_s = getRandSFromSignature(curve, signature_bits.slice(1));
|
||||
var signature_r = r_and_s.r;
|
||||
var signature_s = r_and_s.s;
|
||||
|
||||
// Recover public key using recovery_factor
|
||||
var recovered_public_key_point = recoverPublicKeyPointFromSignature(curve, signature_r, signature_s, hash_bits, recovery_factor);
|
||||
var recovered_public_key = new sjcl.ecc.ecdsa.publicKey(curve, recovered_public_key_point);
|
||||
|
||||
return recovered_public_key;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the r and s components of a signature
|
||||
*
|
||||
* @param {sjcl.ecc.curve} curve
|
||||
* @param {bitArray} signature
|
||||
* @returns {Object} Object with 'r' and 's' fields each as an sjcl.bn
|
||||
*/
|
||||
function getRandSFromSignature(curve, signature) {
|
||||
|
||||
var r_length = curve.r.bitLength();
|
||||
|
||||
return {
|
||||
r: sjcl.bn.fromBits(sjcl.bitArray.bitSlice(signature, 0, r_length)),
|
||||
s: sjcl.bn.fromBits(sjcl.bitArray.bitSlice(signature, r_length, sjcl.bitArray.bitLength(signature)))
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine the recovery factor by trying all four
|
||||
* possibilities and figuring out which results in the
|
||||
* correct public key
|
||||
*
|
||||
* @param {sjcl.ecc.curve} curve
|
||||
* @param {sjcl.bn} r
|
||||
* @param {sjcl.bn} s
|
||||
* @param {bitArray} hash_bits
|
||||
* @param {sjcl.ecc.point} original_public_key_point
|
||||
* @returns {Number, 0-3} Recovery factor
|
||||
*/
|
||||
function calculateRecoveryFactor(curve, r, s, hash_bits, original_public_key_point) {
|
||||
|
||||
var original_public_key_point_bits = original_public_key_point.toBits();
|
||||
|
||||
// TODO: verify that it is possible for the recovery_factor to be 2 or 3,
|
||||
// we may only need 1 bit because the canonical signature might remove the
|
||||
// possibility of us needing to "use the second candidate key"
|
||||
for (var possible_factor = 0; possible_factor < 4; possible_factor++) {
|
||||
|
||||
var resulting_public_key_point;
|
||||
try {
|
||||
resulting_public_key_point = recoverPublicKeyPointFromSignature(curve, r, s, hash_bits, possible_factor);
|
||||
} catch (err) {
|
||||
// console.log(err, err.stack);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sjcl.bitArray.equal(resulting_public_key_point.toBits(), original_public_key_point_bits)) {
|
||||
return possible_factor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw new sjcl.exception.bug('unable to calculate recovery factor from signature');
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Recover the public key from the signature.
|
||||
*
|
||||
* @param {sjcl.ecc.curve} curve
|
||||
* @param {sjcl.bn} r
|
||||
* @param {sjcl.bn} s
|
||||
* @param {bitArray} hash_bits
|
||||
* @param {Number, 0-3} recovery_factor
|
||||
* @returns {sjcl.point} Public key corresponding to signature
|
||||
*/
|
||||
function recoverPublicKeyPointFromSignature(curve, signature_r, signature_s, hash_bits, recovery_factor) {
|
||||
|
||||
var field_order = curve.r;
|
||||
var field_modulus = curve.field.modulus;
|
||||
|
||||
// Reduce the recovery_factor to the two bits used
|
||||
recovery_factor = recovery_factor & 3;
|
||||
|
||||
// The less significant bit specifies whether the y coordinate
|
||||
// of the compressed point is even or not.
|
||||
var compressed_point_y_coord_is_even = recovery_factor & 1;
|
||||
|
||||
// The more significant bit specifies whether we should use the
|
||||
// first or second candidate key.
|
||||
var use_second_candidate_key = recovery_factor >> 1;
|
||||
|
||||
// Calculate (field_order + 1) / 4
|
||||
if (!FIELD_MODULUS_PLUS_ONE_DIVIDED_BY_FOUR) {
|
||||
FIELD_MODULUS_PLUS_ONE_DIVIDED_BY_FOUR = field_modulus.add(1).div(4);
|
||||
}
|
||||
|
||||
// In the paper they write "1. For j from 0 to h do the following..."
|
||||
// That is not necessary here because we are given the recovery_factor
|
||||
// step 1.1 Let x = r + jn
|
||||
// Here "j" is either 0 or 1
|
||||
var x;
|
||||
if (use_second_candidate_key) {
|
||||
x = signature_r.add(field_order);
|
||||
} else {
|
||||
x = signature_r;
|
||||
}
|
||||
|
||||
// step 1.2 and 1.3 convert x to an elliptic curve point
|
||||
// Following formula in section 2.3.4 Octet-String-to-Elliptic-Curve-Point Conversion
|
||||
var alpha = x.mul(x).mul(x).add(curve.a.mul(x)).add(curve.b).mod(field_modulus);
|
||||
var beta = alpha.powermodMontgomery(FIELD_MODULUS_PLUS_ONE_DIVIDED_BY_FOUR, field_modulus);
|
||||
|
||||
// If beta is even but y isn't or
|
||||
// if beta is odd and y is even
|
||||
// then subtract beta from the field_modulus
|
||||
var y;
|
||||
var beta_is_even = beta.mod(2).equals(0);
|
||||
if (beta_is_even && !compressed_point_y_coord_is_even ||
|
||||
!beta_is_even && compressed_point_y_coord_is_even) {
|
||||
y = beta;
|
||||
} else {
|
||||
y = field_modulus.sub(beta);
|
||||
}
|
||||
|
||||
// generated_point_R is the point generated from x and y
|
||||
var generated_point_R = new sjcl.ecc.point(curve, x, y);
|
||||
|
||||
// step 1.4 check that R is valid and R x field_order !== infinity
|
||||
// TODO: add check for R x field_order === infinity
|
||||
if (!generated_point_R.isValidPoint()) {
|
||||
throw new sjcl.exception.corrupt('point R. Not a valid point on the curve. Cannot recover public key');
|
||||
}
|
||||
|
||||
// step 1.5 Compute e from M
|
||||
var message_e = sjcl.bn.fromBits(hash_bits);
|
||||
var message_e_neg = new sjcl.bn(0).sub(message_e).mod(field_order);
|
||||
|
||||
// step 1.6 Compute Q = r^-1 (sR - eG)
|
||||
// console.log('r: ', signature_r);
|
||||
var signature_r_inv = signature_r.inverseMod(field_order);
|
||||
var public_key_point = generated_point_R.mult2(signature_s, message_e_neg, curve.G).mult(signature_r_inv);
|
||||
|
||||
// Validate public key point
|
||||
if (!public_key_point.isValidPoint()) {
|
||||
throw new sjcl.exception.corrupt('public_key_point. Not a valid point on the curve. Cannot recover public key');
|
||||
}
|
||||
|
||||
// Verify that this public key matches the signature
|
||||
if (!verify_raw(curve, message_e, signature_r, signature_s, public_key_point)) {
|
||||
throw new sjcl.exception.corrupt('cannot recover public key');
|
||||
}
|
||||
|
||||
return public_key_point;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Verify a signature given the raw components
|
||||
* using method defined in section 4.1.5:
|
||||
* "Alternative Verifying Operation"
|
||||
*
|
||||
* @param {sjcl.ecc.curve} curve
|
||||
* @param {sjcl.bn} e
|
||||
* @param {sjcl.bn} r
|
||||
* @param {sjcl.bn} s
|
||||
* @param {sjcl.ecc.point} public_key_point
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function verify_raw(curve, e, r, s, public_key_point) {
|
||||
|
||||
var field_order = curve.r;
|
||||
|
||||
// Return false if r is out of bounds
|
||||
if ((new sjcl.bn(1)).greaterEquals(r) || r.greaterEquals(new sjcl.bn(field_order))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return false if s is out of bounds
|
||||
if ((new sjcl.bn(1)).greaterEquals(s) || s.greaterEquals(new sjcl.bn(field_order))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that r = (u1 + u2)G
|
||||
// u1 = e x s^-1 (mod field_order)
|
||||
// u2 = r x s^-1 (mod field_order)
|
||||
var s_mod_inverse_field_order = s.inverseMod(field_order);
|
||||
var u1 = e.mul(s_mod_inverse_field_order).mod(field_order);
|
||||
var u2 = r.mul(s_mod_inverse_field_order).mod(field_order);
|
||||
|
||||
var point_computed = curve.G.mult2(u1, u2, public_key_point);
|
||||
|
||||
return r.equals(point_computed.x.mod(field_order));
|
||||
|
||||
};
|
||||
|
||||
@@ -6,11 +6,9 @@ sjcl.bn.prototype.divRem = function (that) {
|
||||
var thisa = this.abs(), thata = that.abs(), quot = new this._class(0),
|
||||
ci = 0;
|
||||
if (!thisa.greaterEquals(thata)) {
|
||||
this.initWith(0);
|
||||
return this;
|
||||
return [new sjcl.bn(0), this.copy()];
|
||||
} else if (thisa.equals(thata)) {
|
||||
this.initWith(1);
|
||||
return this;
|
||||
return [new sjcl.bn(1), new sjcl.bn(0)];
|
||||
}
|
||||
|
||||
for (; thisa.greaterEquals(thata); ci++) {
|
||||
@@ -45,8 +43,8 @@ sjcl.bn.prototype.div = function (that) {
|
||||
};
|
||||
|
||||
sjcl.bn.prototype.sign = function () {
|
||||
return this.greaterEquals(sjcl.bn.ZERO) ? 1 : -1;
|
||||
};
|
||||
return this.greaterEquals(sjcl.bn.ZERO) ? 1 : -1;
|
||||
};
|
||||
|
||||
/** -this */
|
||||
sjcl.bn.prototype.neg = function () {
|
||||
@@ -59,3 +57,89 @@ sjcl.bn.prototype.abs = function () {
|
||||
return this.neg();
|
||||
} else return this;
|
||||
};
|
||||
|
||||
/** this >> that */
|
||||
sjcl.bn.prototype.shiftRight = function (that) {
|
||||
if ("number" !== typeof that) {
|
||||
throw new Error("shiftRight expects a number");
|
||||
}
|
||||
|
||||
that = +that;
|
||||
|
||||
if (that < 0) {
|
||||
return this.shiftLeft(that);
|
||||
}
|
||||
|
||||
var a = new sjcl.bn(this);
|
||||
|
||||
while (that >= this.radix) {
|
||||
a.limbs.shift();
|
||||
that -= this.radix;
|
||||
}
|
||||
|
||||
while (that--) {
|
||||
a.halveM();
|
||||
}
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
/** this >> that */
|
||||
sjcl.bn.prototype.shiftLeft = function (that) {
|
||||
if ("number" !== typeof that) {
|
||||
throw new Error("shiftLeft expects a number");
|
||||
}
|
||||
|
||||
that = +that;
|
||||
|
||||
if (that < 0) {
|
||||
return this.shiftRight(that);
|
||||
}
|
||||
|
||||
var a = new sjcl.bn(this);
|
||||
|
||||
while (that >= this.radix) {
|
||||
a.limbs.unshift(0);
|
||||
that -= this.radix;
|
||||
}
|
||||
|
||||
while (that--) {
|
||||
a.doubleM();
|
||||
}
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
/** (int)this */
|
||||
// NOTE Truncates to 32-bit integer
|
||||
sjcl.bn.prototype.toNumber = function () {
|
||||
return this.limbs[0] | 0;
|
||||
};
|
||||
|
||||
/** find n-th bit, 0 = LSB */
|
||||
sjcl.bn.prototype.testBit = function (bitIndex) {
|
||||
var limbIndex = Math.floor(bitIndex / this.radix);
|
||||
var bitIndexInLimb = bitIndex % this.radix;
|
||||
|
||||
if (limbIndex >= this.limbs.length) return 0;
|
||||
|
||||
return (this.limbs[limbIndex] >>> bitIndexInLimb) & 1;
|
||||
};
|
||||
|
||||
/** set n-th bit, 0 = LSB */
|
||||
sjcl.bn.prototype.setBitM = function (bitIndex) {
|
||||
var limbIndex = Math.floor(bitIndex / this.radix);
|
||||
var bitIndexInLimb = bitIndex % this.radix;
|
||||
|
||||
while (limbIndex >= this.limbs.length) this.limbs.push(0);
|
||||
|
||||
this.limbs[limbIndex] |= 1 << bitIndexInLimb;
|
||||
|
||||
this.cnormalize();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
sjcl.bn.prototype.modInt = function (n) {
|
||||
return this.toNumber() % n;
|
||||
};
|
||||
|
||||
45
src/js/sjcl-custom/sjcl-jacobi.js
Normal file
45
src/js/sjcl-custom/sjcl-jacobi.js
Normal file
@@ -0,0 +1,45 @@
|
||||
sjcl.bn.prototype.jacobi = function (that) {
|
||||
var a = this;
|
||||
that = new sjcl.bn(that);
|
||||
|
||||
if (that.sign() === -1) return;
|
||||
|
||||
// 1. If a = 0 then return(0).
|
||||
if (a.equals(0)) { return 0; }
|
||||
|
||||
// 2. If a = 1 then return(1).
|
||||
if (a.equals(1)) { return 1; }
|
||||
|
||||
var s = 0;
|
||||
|
||||
// 3. Write a = 2^e * a1, where a1 is odd.
|
||||
var e = 0;
|
||||
while (!a.testBit(e)) e++;
|
||||
var a1 = a.shiftRight(e);
|
||||
|
||||
// 4. If e is even then set s ← 1.
|
||||
if ((e & 1) === 0) {
|
||||
s = 1;
|
||||
} else {
|
||||
var residue = that.modInt(8);
|
||||
|
||||
if (residue === 1 || residue === 7) {
|
||||
// Otherwise set s ← 1 if n ≡ 1 or 7 (mod 8)
|
||||
s = 1;
|
||||
} else if (residue === 3 || residue === 5) {
|
||||
// Or set s ← −1 if n ≡ 3 or 5 (mod 8).
|
||||
s = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. If n ≡ 3 (mod 4) and a1 ≡ 3 (mod 4) then set s ← −s.
|
||||
if (that.modInt(4) === 3 && a1.modInt(4) === 3) {
|
||||
s = -s;
|
||||
}
|
||||
|
||||
if (a1.equals(1)) {
|
||||
return s;
|
||||
} else {
|
||||
return s * that.mod(a1).jacobi(a1);
|
||||
}
|
||||
};
|
||||
156
src/js/sjcl-custom/sjcl-montgomery.js
Normal file
156
src/js/sjcl-custom/sjcl-montgomery.js
Normal file
@@ -0,0 +1,156 @@
|
||||
sjcl.bn.prototype.invDigit = function ()
|
||||
{
|
||||
var radixMod = 1 + this.radixMask;
|
||||
|
||||
if (this.limbs.length < 1) return 0;
|
||||
var x = this.limbs[0];
|
||||
if ((x&1) == 0) return 0;
|
||||
var y = x&3; // y == 1/x mod 2^2
|
||||
y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4
|
||||
y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8
|
||||
y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16
|
||||
// last step - calculate inverse mod DV directly;
|
||||
// assumes 16 < radixMod <= 32 and assumes ability to handle 48-bit ints
|
||||
y = (y*(2-x*y%radixMod))%radixMod; // y == 1/x mod 2^dbits
|
||||
// we really want the negative inverse, and -DV < y < DV
|
||||
return (y>0)?radixMod-y:-y;
|
||||
};
|
||||
|
||||
// returns bit length of the integer x
|
||||
function nbits(x) {
|
||||
var r = 1, t;
|
||||
if((t=x>>>16) != 0) { x = t; r += 16; }
|
||||
if((t=x>>8) != 0) { x = t; r += 8; }
|
||||
if((t=x>>4) != 0) { x = t; r += 4; }
|
||||
if((t=x>>2) != 0) { x = t; r += 2; }
|
||||
if((t=x>>1) != 0) { x = t; r += 1; }
|
||||
return r;
|
||||
}
|
||||
|
||||
// JSBN-style add and multiply for SJCL w/ 24 bit radix
|
||||
sjcl.bn.prototype.am = function (i,x,w,j,c,n) {
|
||||
var xl = x&0xfff, xh = x>>12;
|
||||
while (--n >= 0) {
|
||||
var l = this.limbs[i]&0xfff;
|
||||
var h = this.limbs[i++]>>12;
|
||||
var m = xh*l+h*xl;
|
||||
l = xl*l+((m&0xfff)<<12)+w.limbs[j]+c;
|
||||
c = (l>>24)+(m>>12)+xh*h;
|
||||
w.limbs[j++] = l&0xffffff;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
var Montgomery = function (m)
|
||||
{
|
||||
this.m = m;
|
||||
this.mt = m.limbs.length;
|
||||
this.mt2 = this.mt * 2;
|
||||
this.mp = m.invDigit();
|
||||
this.mpl = this.mp&0x7fff;
|
||||
this.mph = this.mp>>15;
|
||||
this.um = (1<<(m.radix-15))-1;
|
||||
};
|
||||
|
||||
Montgomery.prototype.reduce = function (x)
|
||||
{
|
||||
var radixMod = x.radixMask + 1;
|
||||
while (x.limbs.length <= this.mt2) // pad x so am has enough room later
|
||||
x.limbs[x.limbs.length] = 0;
|
||||
for (var i = 0; i < this.mt; ++i) {
|
||||
// faster way of calculating u0 = x[i]*mp mod 2^radix
|
||||
var j = x.limbs[i]&0x7fff;
|
||||
var u0 = (j*this.mpl+(((j*this.mph+(x.limbs[i]>>15)*this.mpl)&this.um)<<15))&x.radixMask;
|
||||
// use am to combine the multiply-shift-add into one call
|
||||
j = i+this.mt;
|
||||
x.limbs[j] += this.m.am(0,u0,x,i,0,this.mt);
|
||||
// propagate carry
|
||||
while (x.limbs[j] >= radixMod) { x.limbs[j] -= radixMod; x.limbs[++j]++; }
|
||||
}
|
||||
x.trim();
|
||||
x = x.shiftRight(this.mt * this.m.radix);
|
||||
if (x.greaterEquals(this.m)) x = x.sub(this.m);
|
||||
return x.trim().normalize().reduce();
|
||||
};
|
||||
|
||||
Montgomery.prototype.square = function (x)
|
||||
{
|
||||
return this.reduce(x.square());
|
||||
};
|
||||
|
||||
Montgomery.prototype.multiply = function (x, y)
|
||||
{
|
||||
return this.reduce(x.mul(y));
|
||||
};
|
||||
|
||||
Montgomery.prototype.convert = function (x)
|
||||
{
|
||||
return x.abs().shiftLeft(this.mt * this.m.radix).mod(this.m);
|
||||
};
|
||||
|
||||
Montgomery.prototype.revert = function (x)
|
||||
{
|
||||
return this.reduce(x.copy());
|
||||
};
|
||||
|
||||
sjcl.bn.prototype.powermodMontgomery = function (e, m)
|
||||
{
|
||||
var i = e.bitLength(), k, r = new this._class(1);
|
||||
|
||||
if (i <= 0) return r;
|
||||
else if (i < 18) k = 1;
|
||||
else if (i < 48) k = 3;
|
||||
else if (i < 144) k = 4;
|
||||
else if (i < 768) k = 5;
|
||||
else k = 6;
|
||||
|
||||
if (i < 8 || !m.testBit(0)) {
|
||||
// For small exponents and even moduli, use a simple square-and-multiply
|
||||
// algorithm.
|
||||
return this.powermod(e, m);
|
||||
}
|
||||
|
||||
var z = new Montgomery(m);
|
||||
|
||||
e.trim().normalize();
|
||||
|
||||
// precomputation
|
||||
var g = new Array(), n = 3, k1 = k-1, km = (1<<k)-1;
|
||||
g[1] = z.convert(this);
|
||||
if (k > 1) {
|
||||
var g2 = z.square(g[1]);
|
||||
|
||||
while (n <= km) {
|
||||
g[n] = z.multiply(g2, g[n-2]);
|
||||
n += 2;
|
||||
}
|
||||
}
|
||||
|
||||
var j = e.limbs.length-1, w, is1 = true, r2 = new this._class(), t;
|
||||
i = nbits(e.limbs[j])-1;
|
||||
while (j >= 0) {
|
||||
if (i >= k1) w = (e.limbs[j]>>(i-k1))&km;
|
||||
else {
|
||||
w = (e.limbs[j]&((1<<(i+1))-1))<<(k1-i);
|
||||
if (j > 0) w |= e.limbs[j-1]>>(this.radix+i-k1);
|
||||
}
|
||||
|
||||
n = k;
|
||||
while ((w&1) == 0) { w >>= 1; --n; }
|
||||
if ((i -= n) < 0) { i += this.radix; --j; }
|
||||
if (is1) { // ret == 1, don't bother squaring or multiplying it
|
||||
r = g[w].copy();
|
||||
is1 = false;
|
||||
} else {
|
||||
while (n > 1) { r2 = z.square(r); r = z.square(r2); n -= 2; }
|
||||
if (n > 0) r2 = z.square(r); else { t = r; r = r2; r2 = t; }
|
||||
r = z.multiply(r2,g[w]);
|
||||
}
|
||||
|
||||
while (j >= 0 && (e.limbs[j]&(1<<i)) == 0) {
|
||||
r2 = z.square(r); t = r; r = r2; r2 = t;
|
||||
if (--i < 0) { i = this.radix-1; --j; }
|
||||
}
|
||||
}
|
||||
return z.revert(r);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// ----- for secp256k1 ------
|
||||
|
||||
// Overwrite NIST-P256 with secp256k1 so we're on even footing
|
||||
// Overwrite NIST-P256 with secp256k1
|
||||
sjcl.ecc.curves.c256 = new sjcl.ecc.curve(
|
||||
sjcl.bn.pseudoMersennePrime(256, [[0,-1],[4,-1],[6,-1],[7,-1],[8,-1],[9,-1],[32,-1]]),
|
||||
"0x14551231950b75fc4402da1722fc9baee",
|
||||
@@ -62,7 +62,7 @@ sjcl.ecc.pointJac.prototype.doubl = function () {
|
||||
var f = e.square();
|
||||
var x = f.sub(d.copy().doubleM());
|
||||
var y = e.mul(d.sub(x)).subM(c.doubleM().doubleM().doubleM());
|
||||
var z = this.y.mul(this.z).doubleM();
|
||||
var z = this.z.mul(this.y).doubleM();
|
||||
return new sjcl.ecc.pointJac(this.curve, x, y, z);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
sjcl.ecc.ecdsa.secretKey.prototype = {
|
||||
sign: function(hash, paranoia) {
|
||||
var R = this._curve.r,
|
||||
l = R.bitLength(),
|
||||
k = sjcl.bn.random(R.sub(1), paranoia).add(1),
|
||||
r = this._curve.G.mult(k).x.mod(R),
|
||||
s = sjcl.bn.fromBits(hash).add(r.mul(this._exponent)).mul(k.inverseMod(R)).mod(R);
|
||||
sjcl.ecc.ecdsa.secretKey.prototype.sign = function(hash, paranoia, k_for_testing) {
|
||||
var R = this._curve.r,
|
||||
l = R.bitLength();
|
||||
|
||||
return sjcl.bitArray.concat(r.toBits(l), s.toBits(l));
|
||||
// k_for_testing should ONLY BE SPECIFIED FOR TESTING
|
||||
// specifying it will make the signature INSECURE
|
||||
var k;
|
||||
if (typeof k_for_testing === 'object' && k_for_testing.length > 0 && typeof k_for_testing[0] === 'number') {
|
||||
k = k_for_testing;
|
||||
} else if (typeof k_for_testing === 'string' && /^[0-9a-fA-F]+$/.test(k_for_testing)) {
|
||||
k = sjcl.bn.fromBits(sjcl.codec.hex.toBits(k_for_testing));
|
||||
} else {
|
||||
// This is the only option that should be used in production
|
||||
k = sjcl.bn.random(R.sub(1), paranoia).add(1);
|
||||
}
|
||||
|
||||
var r = this._curve.G.mult(k).x.mod(R);
|
||||
var s = sjcl.bn.fromBits(hash).add(r.mul(this._exponent)).mul(k.inverseMod(R)).mod(R);
|
||||
|
||||
return sjcl.bitArray.concat(r.toBits(l), s.toBits(l));
|
||||
};
|
||||
|
||||
sjcl.ecc.ecdsa.publicKey.prototype = {
|
||||
verify: function(hash, rs) {
|
||||
var w = sjcl.bitArray,
|
||||
R = this._curve.r,
|
||||
l = R.bitLength(),
|
||||
r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)),
|
||||
s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)),
|
||||
sInv = s.modInverse(R),
|
||||
hG = sjcl.bn.fromBits(hash).mul(sInv).mod(R),
|
||||
hA = r.mul(sInv).mod(R),
|
||||
r2 = this._curve.G.mult2(hG, hA, this._point).x;
|
||||
sjcl.ecc.ecdsa.publicKey.prototype.verify = function(hash, rs) {
|
||||
var w = sjcl.bitArray,
|
||||
R = this._curve.r,
|
||||
l = R.bitLength(),
|
||||
r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)),
|
||||
s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)),
|
||||
sInv = s.inverseMod(R),
|
||||
hG = sjcl.bn.fromBits(hash).mul(sInv).mod(R),
|
||||
hA = r.mul(sInv).mod(R),
|
||||
r2 = this._curve.G.mult2(hG, hA, this._point).x;
|
||||
|
||||
if (r.equals(0) || s.equals(0) || r.greaterEquals(R) || s.greaterEquals(R) || !r2.equals(r)) {
|
||||
throw (new sjcl.exception.corrupt("signature didn't check out"));
|
||||
}
|
||||
return true;
|
||||
if (r.equals(0) || s.equals(0) || r.greaterEquals(R) || s.greaterEquals(R) || !r2.equals(r)) {
|
||||
throw (new sjcl.exception.corrupt("signature didn't check out"));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -11,8 +11,16 @@ sjcl.ecc.point = function(curve,x,y) {
|
||||
if (x === undefined) {
|
||||
this.isIdentity = true;
|
||||
} else {
|
||||
if (x instanceof sjcl.bn) {
|
||||
x = new curve.field(x);
|
||||
}
|
||||
if (y instanceof sjcl.bn) {
|
||||
y = new curve.field(y);
|
||||
}
|
||||
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
||||
this.isIdentity = false;
|
||||
}
|
||||
this.curve = curve;
|
||||
|
||||
183
test/account-test.js
Normal file
183
test/account-test.js
Normal file
@@ -0,0 +1,183 @@
|
||||
var assert = require('assert');
|
||||
var Account = require('../src/js/ripple/account').Account;
|
||||
|
||||
describe('Account', function(){
|
||||
|
||||
describe('#_publicKeyToAddress()', function(){
|
||||
|
||||
it('should throw an error if the key is invalid', function(){
|
||||
try {
|
||||
Account._publicKeyToAddress('not a real key');
|
||||
} catch (e) {
|
||||
assert(e);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return unchanged a valid UINT160', function(){
|
||||
assert('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz' === Account._publicKeyToAddress('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'));
|
||||
});
|
||||
|
||||
it('should parse a hex-encoded public key as a UINT160', function(){
|
||||
assert('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz' === Account._publicKeyToAddress('025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332'));
|
||||
|
||||
assert('rLpq5RcRzA8FU1yUqEPW4xfsdwon7casuM' === Account._publicKeyToAddress('03BFA879C00D58CF55F2B5975FF9B5293008FF49BEFB3EE6BEE2814247BF561A23'));
|
||||
|
||||
assert('rP4yWwjoDGF2iZSBdAQAgpC449YDezEbT1' === Account._publicKeyToAddress('02DF0AB18930B6410CA9F55CB37541F1FED891B8EDF8AB1D01D8F23018A4B204A7'));
|
||||
|
||||
assert('rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ' === Account._publicKeyToAddress('0310C451A40CAFFD39D6B8A3BD61BF65BCA55246E9DABC3170EBE431D30655B61F'));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// XXX: clean up the stubbed out remote methods
|
||||
|
||||
describe('#publicKeyIsActive()', function(){
|
||||
|
||||
it('should respond true if the public key corresponds to the account address and the master key IS NOT disabled', function(){
|
||||
|
||||
var account = new Account({
|
||||
on: function(){},
|
||||
requestAccountInfo: function(address, callback) {
|
||||
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
|
||||
callback(null, { account_data: {
|
||||
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
|
||||
Flags: 65536,
|
||||
LedgerEntryType: 'AccountRoot'
|
||||
}});
|
||||
}
|
||||
}
|
||||
}, 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz');
|
||||
account.publicKeyIsActive('025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332', function(err, is_valid){
|
||||
assert(err === null);
|
||||
assert(is_valid === true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should respond false if the public key corresponds to the account address and the master key IS disabled', function(){
|
||||
|
||||
var account = new Account({
|
||||
on: function(){},
|
||||
requestAccountInfo: function(address, callback) {
|
||||
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
|
||||
callback(null, { account_data: {
|
||||
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
|
||||
Flags: parseInt(65536 | 0x00100000),
|
||||
LedgerEntryType: 'AccountRoot'
|
||||
}});
|
||||
}
|
||||
}
|
||||
}, 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz');
|
||||
account.publicKeyIsActive('025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332', function(err, is_valid){
|
||||
assert(err === null);
|
||||
assert(is_valid === false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should respond true if the public key corresponds to the regular key', function(){
|
||||
|
||||
var account = new Account({
|
||||
on: function(){},
|
||||
requestAccountInfo: function(address, callback) {
|
||||
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
|
||||
callback(null, { account_data: {
|
||||
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
|
||||
Flags: parseInt(65536 | 0x00100000),
|
||||
LedgerEntryType: 'AccountRoot',
|
||||
RegularKey: 'rNw4ozCG514KEjPs5cDrqEcdsi31Jtfm5r'
|
||||
}});
|
||||
}
|
||||
}
|
||||
}, 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz');
|
||||
account.publicKeyIsActive('02BE53B7ACBB0900E0BB7729C9CAC1033A0137993B17800BD1191BBD1B29D96A8C', function(err, is_valid){
|
||||
assert(err === null);
|
||||
assert(is_valid === true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should respond false if the public key does not correspond to an active public key for the account', function(){
|
||||
|
||||
var account = new Account({
|
||||
on: function(){},
|
||||
requestAccountInfo: function(address, callback) {
|
||||
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
|
||||
callback(null, { account_data: {
|
||||
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
|
||||
Flags: parseInt(65536 | 0x00100000),
|
||||
LedgerEntryType: 'AccountRoot',
|
||||
RegularKey: 'rNw4ozCG514KEjPs5cDrqEcdsi31Jtfm5r'
|
||||
}});
|
||||
}
|
||||
}
|
||||
}, 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz');
|
||||
account.publicKeyIsActive('032ECDA93970BC7E8872EF6582CB52A5557F117244A949EB4FA8AC7688CF24FBC8', function(err, is_valid){
|
||||
assert(err === null);
|
||||
assert(is_valid === false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should respond false if the public key is invalid', function(){
|
||||
|
||||
var account = new Account({
|
||||
on: function(){},
|
||||
requestAccountInfo: function(address, callback) {
|
||||
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
|
||||
callback(null, { account_data: {
|
||||
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
|
||||
Flags: parseInt(65536 | 0x00100000),
|
||||
LedgerEntryType: 'AccountRoot',
|
||||
RegularKey: 'rNw4ozCG514KEjPs5cDrqEcdsi31Jtfm5r'
|
||||
}});
|
||||
}
|
||||
}
|
||||
}, 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz');
|
||||
account.publicKeyIsActive('not a real public key', function(err, is_valid){
|
||||
assert(err);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should assume the master key is valid for unfunded accounts', function(){
|
||||
|
||||
var account = new Account({
|
||||
on: function(){},
|
||||
requestAccountInfo: function(address, callback) {
|
||||
if (address === 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ') {
|
||||
callback({ error: 'remoteError',
|
||||
error_message: 'Remote reported an error.',
|
||||
remote:
|
||||
{ account: 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ',
|
||||
error: 'actNotFound',
|
||||
error_code: 15,
|
||||
error_message: 'Account not found.',
|
||||
id: 3,
|
||||
ledger_current_index: 6391106,
|
||||
request:
|
||||
{ account: 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ',
|
||||
command: 'account_info',
|
||||
id: 3,
|
||||
ident: 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ' },
|
||||
status: 'error',
|
||||
type: 'response' },
|
||||
result: 'remoteError',
|
||||
engine_result: 'remoteError',
|
||||
result_message: 'Remote reported an error.',
|
||||
engine_result_message: 'Remote reported an error.',
|
||||
message: 'Remote reported an error.'
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ');
|
||||
account.publicKeyIsActive('0310C451A40CAFFD39D6B8A3BD61BF65BCA55246E9DABC3170EBE431D30655B61F', function(err, is_valid){
|
||||
assert(!err);
|
||||
assert(is_valid);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
1604
test/amount-test.js
1604
test/amount-test.js
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,19 @@
|
||||
var buster = require("buster");
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var Seed = utils.load_module('seed').Seed;
|
||||
var config = require('./testutils').get_config();
|
||||
|
||||
var Seed = require("../src/js/ripple/seed").Seed;
|
||||
|
||||
try {
|
||||
var conf = require('./config');
|
||||
} catch(exception) {
|
||||
var conf = require('./config-example');
|
||||
}
|
||||
|
||||
var config = require('../src/js/ripple/config').load(conf);
|
||||
|
||||
buster.testCase("Base58", {
|
||||
"Seed" : {
|
||||
"saESc82Vun7Ta5EJRzGJbrXb5HNYk" : function () {
|
||||
var seed = Seed.from_json("saESc82Vun7Ta5EJRzGJbrXb5HNYk");
|
||||
buster.assert.equals(seed.to_hex(), "FF1CF838D02B2CF7B45BAC27F5F24F4F");
|
||||
},
|
||||
"sp6iDHnmiPN7tQFHm5sCW59ax3hfE" : function () {
|
||||
var seed = Seed.from_json("sp6iDHnmiPN7tQFHm5sCW59ax3hfE");
|
||||
buster.assert.equals(seed.to_hex(), "00AD8DA764C3C8AF5F9B8D51C94B9E49");
|
||||
}
|
||||
}
|
||||
describe('Base58', function() {
|
||||
describe('Seed', function() {
|
||||
it('saESc82Vun7Ta5EJRzGJbrXb5HNYk', function () {
|
||||
var seed = Seed.from_json('saESc82Vun7Ta5EJRzGJbrXb5HNYk');
|
||||
assert.strictEqual(seed.to_hex(), 'FF1CF838D02B2CF7B45BAC27F5F24F4F');
|
||||
});
|
||||
it('sp6iDHnmiPN7tQFHm5sCW59ax3hfE', function () {
|
||||
var seed = Seed.from_json('sp6iDHnmiPN7tQFHm5sCW59ax3hfE');
|
||||
assert.strictEqual(seed.to_hex(), '00AD8DA764C3C8AF5F9B8D51C94B9E49');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
var config = module.exports;
|
||||
|
||||
config["ripple-lib"] = {
|
||||
rootPath: "../",
|
||||
environment: "node",
|
||||
tests: [
|
||||
"test/*-test.js"
|
||||
]
|
||||
}
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
279
test/currency-test.js
Normal file
279
test/currency-test.js
Normal file
@@ -0,0 +1,279 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var currency = utils.load_module('currency').Currency;
|
||||
var timeUtil = utils.load_module('utils').time;
|
||||
|
||||
describe('Currency', function() {
|
||||
describe('json_rewrite', function() {
|
||||
it('json_rewrite("USD") == "USD"', function() {
|
||||
assert.strictEqual('USD', currency.json_rewrite('USD'));
|
||||
});
|
||||
it('json_rewrite("NaN") == "XRP"', function() {
|
||||
assert.strictEqual('XRP', currency.json_rewrite(NaN));
|
||||
});
|
||||
it('json_rewrite("015841551A748AD2C1F76FF6ECB0CCCD00000000") == "XAU (-0.5%pa)"', function() {
|
||||
assert.strictEqual(currency.json_rewrite("015841551A748AD2C1F76FF6ECB0CCCD00000000"),
|
||||
"XAU (-0.5%pa)");
|
||||
});
|
||||
});
|
||||
describe('from_json', function() {
|
||||
it('from_json().to_json() == "XRP"', function() {
|
||||
var r = currency.from_json();
|
||||
assert(!r.is_valid());
|
||||
assert.strictEqual('XRP', r.to_json());
|
||||
});
|
||||
it('from_json(NaN).to_json() == "XRP"', function() {
|
||||
var r = currency.from_json(NaN);
|
||||
assert(!r.is_valid());
|
||||
assert.strictEqual('XRP', r.to_json());
|
||||
});
|
||||
it('from_json().to_json("") == "XRP"', function() {
|
||||
var r = currency.from_json('');
|
||||
assert(r.is_valid());
|
||||
assert(r.is_native());
|
||||
assert.strictEqual('XRP', r.to_json());
|
||||
});
|
||||
it('from_json("XRP").to_json() == "XRP"', function() {
|
||||
var r = currency.from_json('XRP');
|
||||
assert(r.is_valid());
|
||||
assert(r.is_native());
|
||||
assert.strictEqual('XRP', r.to_json());
|
||||
});
|
||||
it('from_json("0000000000000000000000000000000000000000").to_json() == "XRP"', function() {
|
||||
var r = currency.from_json('0000000000000000000000000000000000000000');
|
||||
assert(r.is_valid());
|
||||
assert(r.is_native());
|
||||
assert.strictEqual('XRP', r.to_json());
|
||||
});
|
||||
it('from_json("111").to_human()', function() {
|
||||
var r = currency.from_json("111");
|
||||
assert(r.is_valid());
|
||||
assert.strictEqual('111', r.to_json());
|
||||
});
|
||||
it('from_json("1D2").to_human()', function() {
|
||||
var r = currency.from_json("1D2");
|
||||
assert(r.is_valid());
|
||||
assert.strictEqual('1D2', r.to_json());
|
||||
});
|
||||
it('from_json("XAU").to_json() hex', function() {
|
||||
var r = currency.from_json("XAU");
|
||||
assert.strictEqual('0000000000000000000000005841550000000000', r.to_json({force_hex: true}));
|
||||
});
|
||||
it('from_json("XAU (0.5%pa").to_json() hex', function() {
|
||||
var r = currency.from_json("XAU (0.5%pa)");
|
||||
assert.strictEqual('015841550000000041F78E0A28CBF19200000000', r.to_json({force_hex: true}));
|
||||
});
|
||||
it('json_rewrite("015841550000000041F78E0A28CBF19200000000").to_json() hex', function() {
|
||||
var r = currency.json_rewrite('015841550000000041F78E0A28CBF19200000000');
|
||||
assert.strictEqual('XAU (0.5%pa)', r);
|
||||
});
|
||||
it('json_rewrite("015841550000000041F78E0A28CBF19200000000") hex', function() {
|
||||
var r = currency.json_rewrite('015841550000000041F78E0A28CBF19200000000', {force_hex: true});
|
||||
assert.strictEqual('015841550000000041F78E0A28CBF19200000000', r);
|
||||
});
|
||||
});
|
||||
|
||||
describe('from_human', function() {
|
||||
it('From human "USD - Gold (-25%pa)"', function() {
|
||||
var cur = currency.from_human('USD - Gold (-25%pa)');
|
||||
assert.strictEqual(cur.to_json(), 'USD (-25%pa)');
|
||||
assert.strictEqual(cur.to_hex(), '0155534400000000C19A22BC51297F0B00000000');
|
||||
assert.strictEqual(cur.to_json(), cur.to_human());
|
||||
});
|
||||
it('From human "EUR (-0.5%pa)', function() {
|
||||
var cur = currency.from_human('EUR (-0.5%pa)');
|
||||
assert.strictEqual(cur.to_json(), 'EUR (-0.5%pa)');
|
||||
});
|
||||
it('From human "EUR (0.5361%pa)", test decimals', function() {
|
||||
var cur = currency.from_human('EUR (0.5361%pa)');
|
||||
assert.strictEqual(cur.to_json(), 'EUR (0.54%pa)');
|
||||
assert.strictEqual(cur.to_json({decimals:4}), 'EUR (0.5361%pa)');
|
||||
assert.strictEqual(cur.get_interest_percentage_at(undefined, 4), 0.5361);
|
||||
});
|
||||
it('From human "EUR - Euro (0.5361%pa)", test decimals and full_name', function() {
|
||||
var cur = currency.from_human('EUR (0.5361%pa)');
|
||||
assert.strictEqual(cur.to_json(), 'EUR (0.54%pa)');
|
||||
assert.strictEqual(cur.to_json({decimals:4, full_name:'Euro'}), 'EUR - Euro (0.5361%pa)');
|
||||
assert.strictEqual(cur.to_json({decimals:void(0), full_name:'Euro'}), 'EUR - Euro (0.54%pa)');
|
||||
assert.strictEqual(cur.to_json({decimals:undefined, full_name:'Euro'}), 'EUR - Euro (0.54%pa)');
|
||||
assert.strictEqual(cur.to_json({decimals:'henk', full_name:'Euro'}), 'EUR - Euro (0.54%pa)');
|
||||
assert.strictEqual(cur.get_interest_percentage_at(undefined, 4), 0.5361);
|
||||
});
|
||||
it('From human "TYX - 30-Year Treasuries (1.5%pa)"', function() {
|
||||
var cur = currency.from_human('TYX - 30-Year Treasuries (1.5%pa)');
|
||||
assert.strictEqual(cur.to_json(), 'TYX (1.5%pa)');
|
||||
});
|
||||
it('From human "TYX - 30-Year Treasuries"', function() {
|
||||
var cur = currency.from_human('TYX - 30-Year Treasuries');
|
||||
assert.strictEqual(cur.to_json(), 'TYX');
|
||||
});
|
||||
it('From human "INR - Indian Rupees (-0.5%)"', function() {
|
||||
var cur = currency.from_human('INR - Indian Rupees (-0.5%pa)');
|
||||
assert.strictEqual(cur.to_json(), 'INR (-0.5%pa)');
|
||||
});
|
||||
it('From human "INR - 30 Indian Rupees"', function() {
|
||||
var cur = currency.from_human('INR - 30 Indian Rupees');
|
||||
assert.strictEqual(cur.to_json(), 'INR');
|
||||
});
|
||||
it('From human "XRP"', function() {
|
||||
var cur = currency.from_human('XRP');
|
||||
assert.strictEqual(cur.to_json(), 'XRP');
|
||||
assert(cur.is_native(), true);
|
||||
});
|
||||
it('From human "XRP - Ripples"', function() {
|
||||
var cur = currency.from_human('XRP - Ripples');
|
||||
assert.strictEqual(cur.to_json(), 'XRP');
|
||||
assert(cur.is_native(), true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('to_human', function() {
|
||||
it('"USD".to_human() == "USD"', function() {
|
||||
assert.strictEqual('USD', currency.from_json('USD').to_human());
|
||||
});
|
||||
it('"NaN".to_human() == "XRP"', function() {
|
||||
assert.strictEqual('XRP', currency.from_json(NaN).to_human());
|
||||
});
|
||||
it('"015841551A748AD2C1F76FF6ECB0CCCD00000000") == "015841551A748AD2C1F76FF6ECB0CCCD00000000"', function() {
|
||||
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human(), 'XAU (-0.5%pa)');
|
||||
});
|
||||
it('"015841551A748AD2C1F76FF6ECB0CCCD00000000") == "015841551A748AD2C1F76FF6ECB0CCCD00000000"', function() {
|
||||
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({full_name:'Gold'}), 'XAU - Gold (-0.5%pa)');
|
||||
});
|
||||
it('to_human interest XAU with full name, do not show interest', function() {
|
||||
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({full_name:'Gold', show_interest:false}), 'XAU - Gold');
|
||||
});
|
||||
it('to_human interest XAU with full name, show interest', function() {
|
||||
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({full_name:'Gold', show_interest:true}), 'XAU - Gold (-0.5%pa)');
|
||||
});
|
||||
it('to_human interest XAU, do show interest', function() {
|
||||
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({show_interest:true}), 'XAU (-0.5%pa)');
|
||||
});
|
||||
it('to_human interest XAU, do not show interest', function() {
|
||||
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({show_interest:false}), 'XAU');
|
||||
});
|
||||
it('to_human with full_name "USD - US Dollar show interest"', function() {
|
||||
assert.strictEqual(currency.from_json('USD').to_human({full_name:'US Dollar', show_interest:true}), 'USD - US Dollar (0%pa)');
|
||||
});
|
||||
it('to_human with full_name "USD - US Dollar do not show interest"', function() {
|
||||
assert.strictEqual(currency.from_json('USD').to_human({full_name:'US Dollar', show_interest:false}), 'USD - US Dollar');
|
||||
});
|
||||
it('to_human with full_name "USD - US Dollar"', function() {
|
||||
assert.strictEqual('USD - US Dollar', currency.from_json('USD').to_human({full_name:'US Dollar'}));
|
||||
});
|
||||
it('to_human with full_name "XRP - Ripples"', function() {
|
||||
assert.strictEqual('XRP - Ripples', currency.from_json('XRP').to_human({full_name:'Ripples'}));
|
||||
});
|
||||
it('to_human human "TIM" without full_name', function() {
|
||||
var cur = currency.from_json("TIM");
|
||||
assert.strictEqual(cur.to_human(), "TIM");
|
||||
});
|
||||
it('to_human "TIM" with null full_name', function() {
|
||||
var cur = currency.from_json("TIM");
|
||||
assert.strictEqual(cur.to_human({full_name: null}), "TIM");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('from_hex', function() {
|
||||
it('"015841551A748AD2C1F76FF6ECB0CCCD00000000" === "XAU (-0.5%pa)"', function() {
|
||||
var cur = currency.from_hex('015841551A748AD2C1F76FF6ECB0CCCD00000000');
|
||||
assert.strictEqual(cur.to_json(), 'XAU (-0.5%pa)');
|
||||
assert.strictEqual(cur.to_hex(), '015841551A748AD2C1F76FF6ECB0CCCD00000000');
|
||||
assert.strictEqual(cur.to_json(), cur.to_human());
|
||||
});
|
||||
});
|
||||
describe('parse_json', function() {
|
||||
it('should parse a currency object', function() {
|
||||
assert.strictEqual('USD', new currency().parse_json(currency.from_json('USD')).to_json());
|
||||
assert.strictEqual('USD (0.5%pa)', new currency().parse_json(currency.from_json('USD (0.5%pa)')).to_json());
|
||||
});
|
||||
it('should clone for parse_json on itself', function() {
|
||||
var cur = currency.from_json('USD');
|
||||
var cur2 = currency.from_json(cur);
|
||||
assert.strictEqual(cur.to_json(), cur2.to_json());
|
||||
|
||||
cur = currency.from_hex('015841551A748AD2C1F76FF6ECB0CCCD00000000');
|
||||
cur2 = currency.from_json(cur);
|
||||
assert.strictEqual(cur.to_json(), cur2.to_json());
|
||||
});
|
||||
});
|
||||
|
||||
describe('is_valid', function() {
|
||||
it('Currency.is_valid("XRP")', function() {
|
||||
assert(currency.is_valid('XRP'));
|
||||
});
|
||||
it('!Currency.is_valid(NaN)', function() {
|
||||
assert(!currency.is_valid(NaN));
|
||||
});
|
||||
it('from_json("XRP").is_valid()', function() {
|
||||
assert(currency.from_json('XRP').is_valid());
|
||||
});
|
||||
it('!from_json(NaN).is_valid()', function() {
|
||||
assert(!currency.from_json(NaN).is_valid());
|
||||
});
|
||||
});
|
||||
describe('clone', function() {
|
||||
it('should clone currency object', function() {
|
||||
var c = currency.from_json('XRP');
|
||||
assert.strictEqual('XRP', c.clone().to_json());
|
||||
});
|
||||
});
|
||||
describe('to_human', function() {
|
||||
it('should generate human string', function() {
|
||||
assert.strictEqual('XRP', currency.from_json('XRP').to_human());
|
||||
});
|
||||
});
|
||||
describe('has_interest', function() {
|
||||
it('should be true for type 1 currency codes', function() {
|
||||
assert(currency.from_hex('015841551A748AD2C1F76FF6ECB0CCCD00000000').has_interest());
|
||||
assert(currency.from_json('015841551A748AD2C1F76FF6ECB0CCCD00000000').has_interest());
|
||||
});
|
||||
it('should be false for type 0 currency codes', function() {
|
||||
assert(!currency.from_hex('0000000000000000000000005553440000000000').has_interest());
|
||||
assert(!currency.from_json('USD').has_interest());
|
||||
});
|
||||
});
|
||||
function precision(num, precision) {
|
||||
return +(Math.round(num + "e+"+precision) + "e-"+precision);
|
||||
}
|
||||
describe('get_interest_at', function() {
|
||||
it('should return demurred value for demurrage currency', function() {
|
||||
var cur = currency.from_json('015841551A748AD2C1F76FF6ECB0CCCD00000000');
|
||||
|
||||
// At start, no demurrage should occur
|
||||
assert.equal(1, cur.get_interest_at(443845330));
|
||||
assert.equal(1, precision(cur.get_interest_at(new Date(timeUtil.fromRipple(443845330))), 14));
|
||||
|
||||
// After one year, 0.5% should have occurred
|
||||
assert.equal(0.995, precision(cur.get_interest_at(443845330 + 31536000), 14));
|
||||
assert.equal(0.995, precision(cur.get_interest_at(new Date(timeUtil.fromRipple(443845330 + 31536000))), 14));
|
||||
|
||||
// After one demurrage period, 1/e should have occurred
|
||||
assert.equal(1/Math.E, cur.get_interest_at(443845330 + 6291418827.05));
|
||||
|
||||
// One year before start, it should be (roughly) 0.5% higher.
|
||||
assert.equal(1.005, precision(cur.get_interest_at(443845330 - 31536000), 4));
|
||||
|
||||
// One demurrage period before start, rate should be e
|
||||
assert.equal(Math.E, cur.get_interest_at(443845330 - 6291418827.05));
|
||||
});
|
||||
it('should return 0 for currency without interest', function() {
|
||||
var cur = currency.from_json('USD - US Dollar');
|
||||
assert.equal(0, cur.get_interest_at(443845330));
|
||||
assert.equal(0, cur.get_interest_at(443845330 + 31536000));
|
||||
});
|
||||
});
|
||||
describe('get_iso', function() {
|
||||
it('should get "XRP" iso_code', function() {
|
||||
assert.strictEqual('XRP', currency.from_json('XRP').get_iso());
|
||||
});
|
||||
it('should get iso_code', function() {
|
||||
assert.strictEqual('USD', currency.from_json('USD - US Dollar').get_iso());
|
||||
});
|
||||
it('should get iso_code', function() {
|
||||
assert.strictEqual('USD', currency.from_json('USD (0.5%pa)').get_iso());
|
||||
});
|
||||
});
|
||||
});
|
||||
1
test/fixtures/ledger-full-38129.json
vendored
Normal file
1
test/fixtures/ledger-full-38129.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
test/fixtures/ledger-full-40000.json
vendored
Normal file
1
test/fixtures/ledger-full-40000.json
vendored
Normal file
File diff suppressed because one or more lines are too long
14
test/keypair-test.js
Normal file
14
test/keypair-test.js
Normal file
@@ -0,0 +1,14 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var Seed = utils.load_module('seed').Seed;
|
||||
var config = require('./testutils').get_config();
|
||||
|
||||
describe('KeyPair', function() {
|
||||
it('can generate an address', function () {
|
||||
var seed = Seed.from_json("masterpassphrase");
|
||||
var address = seed.get_key().get_address();
|
||||
assert.strictEqual(address.to_json(), 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
88
test/ledger-test.js
Normal file
88
test/ledger-test.js
Normal file
@@ -0,0 +1,88 @@
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
|
||||
var utils = require('./testutils');
|
||||
var Ledger = utils.load_module('ledger').Ledger;
|
||||
var config = require('./testutils').get_config();
|
||||
|
||||
/**
|
||||
* @param ledger_index {Number}
|
||||
* Expects a corresponding ledger dump in $repo/test/fixtures/ folder
|
||||
*/
|
||||
create_ledger_test = function (ledger_index) {
|
||||
describe(String(ledger_index), function() {
|
||||
|
||||
var path = __dirname + '/fixtures/ledger-full-'+ledger_index+'.json';
|
||||
|
||||
var ledger_raw = fs.readFileSync(path),
|
||||
ledger_json = JSON.parse(ledger_raw),
|
||||
ledger = Ledger.from_json(ledger_json);
|
||||
|
||||
it('has account_hash of '+ ledger_json.account_hash, function() {
|
||||
assert.equal(ledger_json.account_hash,
|
||||
ledger.calc_account_hash({sanity_test:true}).to_hex());
|
||||
})
|
||||
it('has transaction_hash of '+ ledger_json.transaction_hash, function() {
|
||||
assert.equal(ledger_json.transaction_hash,
|
||||
ledger.calc_tx_hash().to_hex());
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
describe('Ledger', function() {
|
||||
// This is the first recorded ledger with a non empty transaction set
|
||||
create_ledger_test(38129);
|
||||
// Because, why not.
|
||||
create_ledger_test(40000);
|
||||
|
||||
describe('#calcAccountRootEntryHash', function () {
|
||||
it('will calculate the AccountRoot entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () {
|
||||
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
||||
var expectedEntryHash = '2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8';
|
||||
var actualEntryHash = Ledger.calcAccountRootEntryHash(account);
|
||||
|
||||
assert.equal(actualEntryHash.to_hex(), expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#calcRippleStateEntryHash', function () {
|
||||
it('will calculate the RippleState entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh and rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY in USD', function () {
|
||||
var account1 = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
||||
var account2 = 'rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY';
|
||||
var currency = 'USD';
|
||||
|
||||
var expectedEntryHash = 'C683B5BB928F025F1E860D9D69D6C554C2202DE0D45877ADB3077DA4CB9E125C';
|
||||
var actualEntryHash1 = Ledger.calcRippleStateEntryHash(account1, account2, currency);
|
||||
var actualEntryHash2 = Ledger.calcRippleStateEntryHash(account2, account1, currency);
|
||||
|
||||
assert.equal(actualEntryHash1.to_hex(), expectedEntryHash);
|
||||
assert.equal(actualEntryHash2.to_hex(), expectedEntryHash);
|
||||
});
|
||||
|
||||
it('will calculate the RippleState entry hash for r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV and rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj in UAM', function () {
|
||||
var account1 = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV';
|
||||
var account2 = 'rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj';
|
||||
var currency = 'UAM';
|
||||
|
||||
var expectedEntryHash = 'AE9ADDC584358E5847ADFC971834E471436FC3E9DE6EA1773DF49F419DC0F65E';
|
||||
var actualEntryHash1 = Ledger.calcRippleStateEntryHash(account1, account2, currency);
|
||||
var actualEntryHash2 = Ledger.calcRippleStateEntryHash(account2, account1, currency);
|
||||
|
||||
assert.equal(actualEntryHash1.to_hex(), expectedEntryHash);
|
||||
assert.equal(actualEntryHash2.to_hex(), expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#calcOfferEntryHash', function () {
|
||||
it('will calculate the Offer entry hash for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw, sequence 137', function () {
|
||||
var account = 'r32UufnaCGL82HubijgJGDmdE5hac7ZvLw';
|
||||
var sequence = 137
|
||||
var expectedEntryHash = '03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3';
|
||||
var actualEntryHash = Ledger.calcOfferEntryHash(account, sequence);
|
||||
|
||||
assert.equal(actualEntryHash.to_hex(), expectedEntryHash);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
336
test/message-test.js
Normal file
336
test/message-test.js
Normal file
@@ -0,0 +1,336 @@
|
||||
var assert = require('assert');
|
||||
var sjcl = require('../build/sjcl');
|
||||
var Message = require('../src/js/ripple/message').Message;
|
||||
var Seed = require('../src/js/ripple/seed').Seed;
|
||||
var Remote = require('../src/js/ripple/remote').Remote;
|
||||
|
||||
describe('Message', function(){
|
||||
|
||||
describe('signMessage', function(){
|
||||
|
||||
it('should prepend the MAGIC_BYTES, call the HASH_FUNCTION, and then call signHash', function(){
|
||||
|
||||
var normal_signHash = Message.signHash;
|
||||
|
||||
var message_text = 'Hello World!';
|
||||
|
||||
var signHash_called = false;
|
||||
Message.signHash = function(hash) {
|
||||
signHash_called = true;
|
||||
assert.deepEqual(hash, Message.HASH_FUNCTION(Message.MAGIC_BYTES + message_text));
|
||||
};
|
||||
|
||||
Message.signMessage(message_text);
|
||||
assert(signHash_called);
|
||||
|
||||
Message.signHash = normal_signHash;
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('signHash', function(){
|
||||
|
||||
it('should accept the hash as either a hex string or a bitArray', function(){
|
||||
|
||||
var normal_random = sjcl.random.randomWords;
|
||||
|
||||
sjcl.random.randomWords = function(num_words){
|
||||
var words = [];
|
||||
for (var w = 0; w < num_words; w++) {
|
||||
words.push(sjcl.codec.hex.toBits('00000000'));
|
||||
}
|
||||
return words;
|
||||
};
|
||||
|
||||
var secret_string = 'safRpB5euNL52PZPTSqrE9gvuFwTC';
|
||||
// var address = 'rLLzaq61D633b5hhbNXKM9CkrYHboobVv3';
|
||||
var hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778';
|
||||
|
||||
var signature1 = Message.signHash(hash, secret_string);
|
||||
var signature2 = Message.signHash(sjcl.codec.hex.toBits(hash), secret_string);
|
||||
|
||||
assert.strictEqual(signature1, signature2);
|
||||
|
||||
sjcl.random.randomWords = normal_random;
|
||||
|
||||
});
|
||||
|
||||
it('should accept the secret as a string or scjl.ecc.ecdsa.secretKey object', function(){
|
||||
|
||||
var normal_random = sjcl.random.randomWords;
|
||||
|
||||
sjcl.random.randomWords = function(num_words){
|
||||
var words = [];
|
||||
for (var w = 0; w < num_words; w++) {
|
||||
words.push(sjcl.codec.hex.toBits('00000000'));
|
||||
}
|
||||
return words;
|
||||
};
|
||||
|
||||
var secret_string = 'safRpB5euNL52PZPTSqrE9gvuFwTC';
|
||||
// var address = 'rLLzaq61D633b5hhbNXKM9CkrYHboobVv3';
|
||||
var hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778';
|
||||
|
||||
var signature1 = Message.signHash(hash, secret_string);
|
||||
var signature2 = Message.signHash(hash, Seed.from_json(secret_string).get_key()._secret);
|
||||
|
||||
assert.strictEqual(signature1, signature2);
|
||||
|
||||
sjcl.random.randomWords = normal_random;
|
||||
|
||||
});
|
||||
|
||||
it('should throw an error if given an invalid secret key', function(){
|
||||
// Annoyingly non hex can be fed to the BigInteger(s, 16) constructor and
|
||||
// it will parse as a number. Before the commit of this comment, this test
|
||||
// involved a fixture of 32 chars, which was assumed to be hex. The test
|
||||
// passed, but for the wrong wreasons. There was a bug in Seed.parse_json.
|
||||
|
||||
// Seed.from_json only creates invalid seeds from empty strings or invalid
|
||||
// base58 starting with an s, which it tries to base 58 decode/check sum.
|
||||
// The rest will be assumed to be a passphrase.
|
||||
|
||||
// This is a bad b58 seed
|
||||
var secret_string = 'sbadsafRpB5euNL52PZPTSqrE9gvuFwTC';
|
||||
var hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778';
|
||||
|
||||
assert.throws(function(){
|
||||
Message.signHash(hash, secret_string);
|
||||
}, /Cannot\ generate\ keys\ from\ invalid\ seed/);
|
||||
|
||||
});
|
||||
|
||||
it('should throw an error if the parameters are reversed', function(){
|
||||
|
||||
var secret_string = 'safRpB5euNL52PZPTSqrE9gvuFwTC';
|
||||
var hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778';
|
||||
|
||||
assert.throws(function(){
|
||||
Message.signHash(secret_string, hash);
|
||||
}, Error);
|
||||
|
||||
assert.throws(function(){
|
||||
Message.signHash(secret_string, sjcl.codec.hex.toBits(hash));
|
||||
}, Error);
|
||||
|
||||
assert.throws(function(){
|
||||
Message.signHash(Seed.from_json(secret_string).get_key()._secret, hash);
|
||||
}, Error);
|
||||
|
||||
assert.throws(function(){
|
||||
Message.signHash(Seed.from_json(secret_string).get_key()._secret, sjcl.codec.hex.toBits(hash));
|
||||
}, Error);
|
||||
|
||||
});
|
||||
|
||||
it('should produce a base64-encoded signature', function(){
|
||||
var REGEX_BASE64 = /^([A-Za-z0-9\+]{4})*([A-Za-z0-9\+]{2}==)|([A-Za-z0-9\+]{3}=)?$/;
|
||||
|
||||
var normal_random = sjcl.random.randomWords;
|
||||
|
||||
sjcl.random.randomWords = function(num_words){
|
||||
var words = [];
|
||||
for (var w = 0; w < num_words; w++) {
|
||||
words.push(sjcl.codec.hex.toBits('00000000'));
|
||||
}
|
||||
return words;
|
||||
};
|
||||
|
||||
var secret_string = 'safRpB5euNL52PZPTSqrE9gvuFwTC';
|
||||
// var address = 'rLLzaq61D633b5hhbNXKM9CkrYHboobVv3';
|
||||
var hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778';
|
||||
|
||||
var signature = Message.signHash(hash, secret_string);
|
||||
|
||||
assert(REGEX_BASE64.test(signature));
|
||||
|
||||
sjcl.random.randomWords = normal_random;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('verifyMessageSignature', function(){
|
||||
|
||||
it('should prepend the MAGIC_BYTES, call the HASH_FUNCTION, and then call verifyHashSignature', function(){
|
||||
|
||||
var normal_verifyHashSignature = Message.verifyHashSignature;
|
||||
|
||||
var data = {
|
||||
message: 'Hello world!',
|
||||
signature: 'AAAAGzFa1pYjhssCpDFZgFSnYQ8qCnMkLaZrg0mXZyNQ2NxgMQ8z9U3ngYerxSZCEt3Q4raMIpt03db7jDNGbfmHy8I='
|
||||
};
|
||||
|
||||
var verifyHashSignature_called = false;
|
||||
Message.verifyHashSignature = function(vhs_data, remote, callback) {
|
||||
verifyHashSignature_called = true;
|
||||
|
||||
assert.deepEqual(vhs_data.hash, Message.HASH_FUNCTION(Message.MAGIC_BYTES + data.message));
|
||||
assert.strictEqual(vhs_data.signature, data.signature);
|
||||
callback();
|
||||
|
||||
};
|
||||
|
||||
Message.verifyMessageSignature(data, {}, function(err){
|
||||
assert(!err);
|
||||
});
|
||||
assert(verifyHashSignature_called);
|
||||
|
||||
Message.verifyHashSignature = normal_verifyHashSignature;
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('verifyHashSignature', function(){
|
||||
|
||||
it('should throw an error if a callback function is not supplied', function(){
|
||||
|
||||
var data = {
|
||||
message: 'Hello world!',
|
||||
hash: '861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8',
|
||||
signature: 'AAAAHOUJQzG/7BO82fGNt1TNE+GGVXKuQQ0N2nTO+iJETE69PiHnaAkkOzovM177OosxbKjpt3KvwuJflgUB2YGvgjk=',
|
||||
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
|
||||
};
|
||||
|
||||
//Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote();
|
||||
|
||||
assert.throws(function(){
|
||||
Message.verifyHashSignature(data);
|
||||
}, /(?=.*callback\ function).*/);
|
||||
});
|
||||
|
||||
it('should respond with an error if the hash is missing or invalid', function(done){
|
||||
|
||||
var data = {
|
||||
message: 'Hello world!',
|
||||
signature: 'AAAAHOUJQzG/7BO82fGNt1TNE+GGVXKuQQ0N2nTO+iJETE69PiHnaAkkOzovM177OosxbKjpt3KvwuJflgUB2YGvgjk=',
|
||||
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
|
||||
};
|
||||
|
||||
//Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote();
|
||||
test_remote.state = 'online';
|
||||
|
||||
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
||||
assert(/hash/i.test(err.message));
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should respond with an error if the account is missing or invalid', function(done){
|
||||
|
||||
var data = {
|
||||
message: 'Hello world!',
|
||||
hash: '861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8',
|
||||
signature: 'AAAAHOUJQzG/7BO82fGNt1TNE+GGVXKuQQ0N2nTO+iJETE69PiHnaAkkOzovM177OosxbKjpt3KvwuJflgUB2YGvgjk='
|
||||
};
|
||||
|
||||
//Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote();
|
||||
test_remote.state = 'online';
|
||||
|
||||
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
||||
assert(/account|address/i.test(err.message));
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should respond with an error if the signature is missing or invalid', function(done){
|
||||
|
||||
var data = {
|
||||
message: 'Hello world!',
|
||||
hash: '861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8',
|
||||
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'
|
||||
};
|
||||
|
||||
//Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote();
|
||||
test_remote.state = 'online';
|
||||
|
||||
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
||||
assert(/signature/i.test(err.message));
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should respond true if the signature is valid and corresponds to an active public key for the account', function(done){
|
||||
|
||||
var data = {
|
||||
message: 'Hello world!',
|
||||
hash: 'e9a82ea40514787918959b1100481500a5d384030f8770575c6a587675025fe212e6623e25643f251666a7b8b23af476c2850a8ea92153de5724db432892c752',
|
||||
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
|
||||
signature: 'AAAAHMIPCQGLgdnpX1Ccv1wHb56H4NggxIM6U08Qkb9mUjN2Vn9pZ3CHvq1yWLBi6NqpW+7kedLnmfu4VG2+y43p4Xs='
|
||||
};
|
||||
|
||||
//Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote();
|
||||
test_remote.state = 'online';
|
||||
test_remote.requestAccountInfo = function(options, callback) {
|
||||
var account = options.account;
|
||||
if (account === data.account) {
|
||||
callback(null, {
|
||||
"account_data": {
|
||||
"Account": "rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz",
|
||||
"Flags": 1114112,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"RegularKey": "rHq2wyUtLkAad3vURUk33q9gozd97skhSf"
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback(new Error('wrong account'));
|
||||
}
|
||||
};
|
||||
|
||||
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
||||
assert(!err);
|
||||
assert(valid);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should respond false if a key can be recovered from the signature but it does not correspond to an active public key', function(done){
|
||||
|
||||
// Signature created by disabled master key
|
||||
var data = {
|
||||
message: 'Hello world!',
|
||||
hash: 'e9a82ea40514787918959b1100481500a5d384030f8770575c6a587675025fe212e6623e25643f251666a7b8b23af476c2850a8ea92153de5724db432892c752',
|
||||
account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
|
||||
signature: 'AAAAG+dB/rAjZ5m8eQ/opcqQOJsFbKxOu9jq9KrOAlNO4OdcBDXyCBlkZqS9Xr8oZI2uh0boVsgYOS3pOLJz+Dh3Otk='
|
||||
};
|
||||
|
||||
//Remote.prototype.addServer = function(){};
|
||||
var test_remote = new Remote();
|
||||
test_remote.state = 'online';
|
||||
test_remote.requestAccountInfo = function(options, callback) {
|
||||
var account = options.account;
|
||||
if (account === data.account) {
|
||||
callback(null, {
|
||||
"account_data": {
|
||||
"Account": "rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz",
|
||||
"Flags": 1114112,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"RegularKey": "rHq2wyUtLkAad3vURUk33q9gozd97skhSf"
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback(new Error('wrong account'));
|
||||
}
|
||||
};
|
||||
|
||||
Message.verifyHashSignature(data, test_remote, function(err, valid){
|
||||
assert(!err);
|
||||
assert(!valid);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
1553
test/orderbook-test.js
Normal file
1553
test/orderbook-test.js
Normal file
File diff suppressed because it is too large
Load Diff
497
test/remote-test.js
Normal file
497
test/remote-test.js
Normal file
@@ -0,0 +1,497 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
|
||||
var Remote = utils.load_module('remote').Remote;
|
||||
var Server = utils.load_module('server').Server;
|
||||
var Request = utils.load_module('request').Request;
|
||||
|
||||
var options, remote, callback, database, tx;
|
||||
|
||||
var ADDRESS = 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS';
|
||||
var PEER_ADDRESS = 'rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX';
|
||||
var LEDGER_INDEX = 9592219;
|
||||
var LEDGER_HASH = 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE';
|
||||
var PAGING_MARKER = '29F992CC252056BF690107D1E8F2D9FBAFF29FF107B62B1D1F4E4E11ADF2CC73';
|
||||
|
||||
|
||||
describe('Remote', function () {
|
||||
beforeEach(function () {
|
||||
options = {
|
||||
trace : true,
|
||||
trusted: true,
|
||||
local_signing: true,
|
||||
|
||||
servers: [
|
||||
{ host: 's-west.ripple.com', port: 443, secure: true },
|
||||
{ host: 's-east.ripple.com', port: 443, secure: true }
|
||||
],
|
||||
|
||||
blobvault : 'https://blobvault.payward.com',
|
||||
persistent_auth : false,
|
||||
transactions_per_page: 50,
|
||||
|
||||
bridge: {
|
||||
out: {
|
||||
// 'bitcoin': 'localhost:3000'
|
||||
// 'bitcoin': 'https://www.bitstamp.net/ripple/bridge/out/bitcoin/'
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
it('remote server initialization - url object', function() {
|
||||
var remote = new Remote({
|
||||
servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ],
|
||||
});
|
||||
assert(Array.isArray(remote._servers));
|
||||
assert(remote._servers[0] instanceof Server);
|
||||
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
|
||||
})
|
||||
|
||||
it('remote server initialization - url object - no secure property', function() {
|
||||
var remote = new Remote({
|
||||
servers: [ { host: 's-west.ripple.com', port: 443 } ]
|
||||
});
|
||||
assert(Array.isArray(remote._servers));
|
||||
assert(remote._servers[0] instanceof Server);
|
||||
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
|
||||
})
|
||||
|
||||
it('remote server initialization - url object - secure: false', function() {
|
||||
var remote = new Remote({
|
||||
servers: [ { host: 's-west.ripple.com', port: 443, secure: false } ]
|
||||
});
|
||||
assert(Array.isArray(remote._servers));
|
||||
assert(remote._servers[0] instanceof Server);
|
||||
assert.strictEqual(remote._servers[0]._url, 'ws://s-west.ripple.com:443');
|
||||
});
|
||||
|
||||
it('remote server initialization - url object - string port', function() {
|
||||
var remote = new Remote({
|
||||
servers: [ { host: 's-west.ripple.com', port: '443', secure: true } ]
|
||||
});
|
||||
assert(Array.isArray(remote._servers));
|
||||
assert(remote._servers[0] instanceof Server);
|
||||
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
|
||||
})
|
||||
|
||||
it('remote server initialization - url object - invalid host', function() {
|
||||
assert.throws(
|
||||
function() {
|
||||
var remote = new Remote({
|
||||
servers: [ { host: '+', port: 443, secure: true } ]
|
||||
});
|
||||
}, Error);
|
||||
})
|
||||
|
||||
it('remote server initialization - url object - invalid port', function() {
|
||||
assert.throws(
|
||||
function() {
|
||||
var remote = new Remote({
|
||||
servers: [ { host: 's-west.ripple.com', port: null, secure: true } ]
|
||||
});
|
||||
}, TypeError);
|
||||
});
|
||||
|
||||
it('remote server initialization - url object - port out of range', function() {
|
||||
assert.throws(
|
||||
function() {
|
||||
var remote = new Remote({
|
||||
servers: [ { host: 's-west.ripple.com', port: 65537, secure: true } ]
|
||||
});
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it('remote server initialization - url string', function() {
|
||||
var remote = new Remote({
|
||||
servers: [ 'wss://s-west.ripple.com:443' ]
|
||||
});
|
||||
assert(Array.isArray(remote._servers));
|
||||
assert(remote._servers[0] instanceof Server);
|
||||
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
|
||||
});
|
||||
|
||||
it('remote server initialization - url string - ws://', function() {
|
||||
var remote = new Remote({
|
||||
servers: [ 'ws://s-west.ripple.com:443' ]
|
||||
});
|
||||
assert(Array.isArray(remote._servers));
|
||||
assert(remote._servers[0] instanceof Server);
|
||||
assert.strictEqual(remote._servers[0]._url, 'ws://s-west.ripple.com:443');
|
||||
});
|
||||
|
||||
it('remote server initialization - url string - invalid host', function() {
|
||||
assert.throws(
|
||||
function() {
|
||||
var remote = new Remote({
|
||||
servers: [ 'ws://+:443' ]
|
||||
});
|
||||
}, Error
|
||||
);
|
||||
});
|
||||
|
||||
it('remote server initialization - url string - invalid port', function() {
|
||||
assert.throws(
|
||||
function() {
|
||||
var remote = new Remote({
|
||||
servers: [ 'ws://s-west.ripple.com:null' ]
|
||||
});
|
||||
}, Error
|
||||
);
|
||||
});
|
||||
|
||||
it('remote server initialization - url string - port out of range', function() {
|
||||
assert.throws(
|
||||
function() {
|
||||
var remote = new Remote({
|
||||
servers: [ 'ws://s-west.ripple.com:65537:' ]
|
||||
});
|
||||
}, Error
|
||||
);
|
||||
});
|
||||
|
||||
describe('request constructors', function () {
|
||||
beforeEach(function () {
|
||||
callback = function () {}
|
||||
remote = new Remote(options);
|
||||
});
|
||||
|
||||
it('requesting a ledger', function () {
|
||||
var request = remote.request_ledger(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
});
|
||||
|
||||
it('requesting server info', function () {
|
||||
var request = remote.request_server_info(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
})
|
||||
|
||||
it('requesting peers', function () {
|
||||
var request = remote.request_peers(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
});
|
||||
|
||||
it('requesting a connection', function () {
|
||||
var request = remote.request_connect(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
});
|
||||
|
||||
it('making a unique node list add request', function () {
|
||||
var request = remote.request_unl_add(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
});
|
||||
|
||||
it('making a unique node list request', function () {
|
||||
var request = remote.request_unl_list(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
});
|
||||
|
||||
it('making a unique node list delete request', function () {
|
||||
var request = remote.request_unl_delete(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
});
|
||||
|
||||
it('request account currencies with ledger index', function() {
|
||||
var request = remote.requestAccountCurrencies({account: ADDRESS});
|
||||
assert.strictEqual(request.message.command, 'account_currencies');
|
||||
assert.strictEqual(request.message.account, ADDRESS);
|
||||
});
|
||||
|
||||
it('request account info with ledger index', function() {
|
||||
var request = remote.requestAccountInfo({account: ADDRESS, ledger: 9592219});
|
||||
assert.strictEqual(request.message.command, 'account_info');
|
||||
assert.strictEqual(request.message.account, ADDRESS);
|
||||
assert.strictEqual(request.message.ledger_index, 9592219);
|
||||
});
|
||||
it('request account info with ledger hash', function() {
|
||||
var request = remote.requestAccountInfo({account: ADDRESS, ledger: LEDGER_HASH});
|
||||
assert.strictEqual(request.message.command, 'account_info');
|
||||
assert.strictEqual(request.message.account, ADDRESS);
|
||||
assert.strictEqual(request.message.ledger_hash, LEDGER_HASH);
|
||||
});
|
||||
it('request account info with ledger identifier', function() {
|
||||
var request = remote.requestAccountInfo({account: ADDRESS, ledger: 'validated'});
|
||||
assert.strictEqual(request.message.command, 'account_info');
|
||||
assert.strictEqual(request.message.account, ADDRESS);
|
||||
assert.strictEqual(request.message.ledger_index, 'validated');
|
||||
});
|
||||
|
||||
it('request account balance with ledger index', function() {
|
||||
var request = remote.requestAccountBalance(ADDRESS, 9592219);
|
||||
assert.strictEqual(request.message.command, 'ledger_entry');
|
||||
assert.strictEqual(request.message.account_root, ADDRESS);
|
||||
assert.strictEqual(request.message.ledger_index, 9592219);
|
||||
});
|
||||
it('request account balance with ledger hash', function() {
|
||||
var request = remote.requestAccountBalance(ADDRESS, LEDGER_HASH);
|
||||
assert.strictEqual(request.message.command, 'ledger_entry');
|
||||
assert.strictEqual(request.message.account_root, ADDRESS);
|
||||
assert.strictEqual(request.message.ledger_hash, LEDGER_HASH);
|
||||
});
|
||||
it('request account balance with ledger identifier', function() {
|
||||
var request = remote.requestAccountBalance(ADDRESS, 'validated');
|
||||
assert.strictEqual(request.message.command, 'ledger_entry');
|
||||
assert.strictEqual(request.message.account_root, ADDRESS);
|
||||
assert.strictEqual(request.message.ledger_index, 'validated');
|
||||
});
|
||||
});
|
||||
|
||||
it('pagingAccountRequest', function() {
|
||||
var request = Remote.accountRequest('account_lines', {account: ADDRESS});
|
||||
assert.deepEqual(request.message, {
|
||||
command: 'account_lines',
|
||||
id: undefined,
|
||||
account: ADDRESS
|
||||
});
|
||||
});
|
||||
|
||||
it('pagingAccountRequest - limit', function() {
|
||||
var request = Remote.accountRequest('account_lines', {account: ADDRESS, limit: 100});
|
||||
assert.deepEqual(request.message, {
|
||||
command: 'account_lines',
|
||||
id: undefined,
|
||||
account: ADDRESS,
|
||||
limit: 100
|
||||
});
|
||||
});
|
||||
|
||||
it('pagingAccountRequest - limit, marker', function() {
|
||||
var request = Remote.accountRequest('account_lines', {account: ADDRESS, limit: 100, marker: PAGING_MARKER, ledger: 9592219});
|
||||
assert.deepEqual(request.message, {
|
||||
command: 'account_lines',
|
||||
id: undefined,
|
||||
account: ADDRESS,
|
||||
limit: 100,
|
||||
marker: PAGING_MARKER,
|
||||
ledger_index: 9592219
|
||||
});
|
||||
|
||||
assert(!request.requested);
|
||||
});
|
||||
|
||||
it('accountRequest - limit min', function() {
|
||||
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: 0}).message.limit, 0);
|
||||
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: -1}).message.limit, 0);
|
||||
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: -1e9}).message.limit, 0);
|
||||
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: -1e24}).message.limit, 0);
|
||||
});
|
||||
|
||||
it('accountRequest - limit max', function() {
|
||||
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: 1e9}).message.limit, 1e9);
|
||||
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: 1e9+1}).message.limit, 1e9);
|
||||
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: 1e10}).message.limit, 1e9);
|
||||
assert.strictEqual(Remote.accountRequest('account_lines', {account: ADDRESS, limit: 1e24}).message.limit, 1e9);
|
||||
});
|
||||
|
||||
it('accountRequest - a valid ledger is required when using a marker', function() {
|
||||
assert.throws(function() {
|
||||
Remote.accountRequest('account_lines', {account: ADDRESS, marker: PAGING_MARKER})
|
||||
},'A ledger_index or ledger_hash must be provided when using a marker');
|
||||
|
||||
assert.throws(function() {
|
||||
Remote.accountRequest('account_lines', {account: ADDRESS, marker: PAGING_MARKER, ledger:'validated'})
|
||||
},'A ledger_index or ledger_hash must be provided when using a marker');
|
||||
|
||||
assert.throws(function() {
|
||||
Remote.accountRequest('account_lines', {account: ADDRESS, marker: PAGING_MARKER, ledger:NaN})
|
||||
},'A ledger_index or ledger_hash must be provided when using a marker');
|
||||
|
||||
assert.throws(function() {
|
||||
Remote.accountRequest('account_lines', {account: ADDRESS, marker: PAGING_MARKER, ledger:LEDGER_HASH.substr(0,63)})
|
||||
},'A ledger_index or ledger_hash must be provided when using a marker');
|
||||
|
||||
assert.throws(function() {
|
||||
Remote.accountRequest('account_lines', {account: ADDRESS, marker: PAGING_MARKER, ledger:LEDGER_HASH+'F'})
|
||||
},'A ledger_index or ledger_hash must be provided when using a marker');
|
||||
});
|
||||
|
||||
it('requestAccountLines, account and callback', function() {
|
||||
var callback = function() {};
|
||||
var remote = new Remote({
|
||||
servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ]
|
||||
});
|
||||
var request = remote.requestAccountLines(
|
||||
{account: ADDRESS},
|
||||
callback
|
||||
);
|
||||
|
||||
assert.deepEqual(request.message, {
|
||||
command: 'account_lines',
|
||||
id: undefined,
|
||||
account: ADDRESS
|
||||
});
|
||||
|
||||
assert(request.requested);
|
||||
});
|
||||
|
||||
it('requestAccountLines, ledger, peer', function() {
|
||||
var callback = function() {};
|
||||
var remote = new Remote({
|
||||
servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ]
|
||||
});
|
||||
var request = remote.requestAccountLines(
|
||||
{
|
||||
account: ADDRESS,
|
||||
ledger: LEDGER_HASH,
|
||||
peer: PEER_ADDRESS
|
||||
},
|
||||
callback
|
||||
);
|
||||
|
||||
assert.deepEqual(request.message, {
|
||||
command: 'account_lines',
|
||||
id: undefined,
|
||||
account: ADDRESS,
|
||||
ledger_hash: LEDGER_HASH,
|
||||
peer: PEER_ADDRESS
|
||||
});
|
||||
|
||||
assert(request.requested);
|
||||
});
|
||||
|
||||
it('requestAccountLines, ledger, peer, limit and marker', function() {
|
||||
var callback = function() {};
|
||||
var remote = new Remote({
|
||||
servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ]
|
||||
});
|
||||
var request = remote.requestAccountLines(
|
||||
{
|
||||
account: ADDRESS,
|
||||
ledger: LEDGER_INDEX,
|
||||
peer: PEER_ADDRESS,
|
||||
limit: 200,
|
||||
marker: PAGING_MARKER
|
||||
},
|
||||
callback
|
||||
);
|
||||
|
||||
assert.deepEqual(request.message, {
|
||||
command: 'account_lines',
|
||||
id: undefined,
|
||||
account: ADDRESS,
|
||||
ledger_index: LEDGER_INDEX,
|
||||
peer: PEER_ADDRESS,
|
||||
limit: 200,
|
||||
marker: PAGING_MARKER
|
||||
});
|
||||
|
||||
assert(request.requested);
|
||||
});
|
||||
|
||||
it('requestAccountOffers, ledger, peer, limit and marker', function() {
|
||||
var callback = function() {};
|
||||
var remote = new Remote({
|
||||
servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ]
|
||||
});
|
||||
var request = remote.requestAccountOffers(
|
||||
{
|
||||
account: ADDRESS,
|
||||
ledger: LEDGER_HASH,
|
||||
peer: PEER_ADDRESS,
|
||||
limit: 32,
|
||||
marker: PAGING_MARKER
|
||||
},
|
||||
callback
|
||||
);
|
||||
|
||||
assert.deepEqual(request.message, {
|
||||
command: 'account_offers',
|
||||
id: undefined,
|
||||
account: ADDRESS,
|
||||
ledger_hash: LEDGER_HASH,
|
||||
peer: PEER_ADDRESS,
|
||||
limit: 32,
|
||||
marker: PAGING_MARKER
|
||||
});
|
||||
|
||||
assert(request.requested);
|
||||
});
|
||||
|
||||
it('create remote and get pending transactions', function() {
|
||||
before(function() {
|
||||
tx = [{
|
||||
tx_json: {
|
||||
Account : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
Amount : {
|
||||
currency : "LTC",
|
||||
issuer : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
value : "9.985"
|
||||
},
|
||||
Destination : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
Fee : "15",
|
||||
Flags : 0,
|
||||
Paths : [
|
||||
[
|
||||
{
|
||||
account : "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
currency : "USD",
|
||||
issuer : "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
type : 49,
|
||||
type_hex : "0000000000000031"
|
||||
},
|
||||
{
|
||||
currency : "LTC",
|
||||
issuer : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||
type : 48,
|
||||
type_hex : "0000000000000030"
|
||||
},
|
||||
{
|
||||
account : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||
currency : "LTC",
|
||||
issuer : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||
type : 49,
|
||||
type_hex : "0000000000000031"
|
||||
}
|
||||
]
|
||||
],
|
||||
SendMax : {
|
||||
currency : "USD",
|
||||
issuer : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
value : "30.30993068"
|
||||
},
|
||||
Sequence : 415,
|
||||
SigningPubKey : "02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A",
|
||||
TransactionType : "Payment",
|
||||
TxnSignature : "304602210096C2F385530587DE573936CA51CB86B801A28F777C944E268212BE7341440B7F022100EBF0508A9145A56CDA7FAF314DF3BBE51C6EE450BA7E74D88516891A3608644E"
|
||||
},
|
||||
clientID: '48631',
|
||||
state: 'pending',
|
||||
submitIndex: 1,
|
||||
submittedIDs: ["304602210096C2F385530587DE573936CA51CB86B801A28F777C944E268212BE7341440B7F022100EBF0508A9145A56CDA7FAF314DF3BBE51C6EE450BA7E74D88516891A3608644E"],
|
||||
secret: 'mysecret'
|
||||
}];
|
||||
database = {
|
||||
getPendingTransactions: function(callback) {
|
||||
callback(null, tx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should set transaction members correct ', function(done) {
|
||||
remote = new Remote(options);
|
||||
remote.storage = database;
|
||||
remote.transaction = function() {
|
||||
return {
|
||||
clientID: function(id) {
|
||||
if (typeof id === 'string') {
|
||||
this._clientID = id;
|
||||
}
|
||||
return this;
|
||||
},
|
||||
submit: function() {
|
||||
assert.deepEqual(this._clientID, tx[0].clientID);
|
||||
assert.deepEqual(this.submittedIDs,[tx[0].tx_json.TxnSignature]);
|
||||
assert.equal(this.submitIndex, tx[0].submitIndex);
|
||||
assert.equal(this.secret, tx[0].secret);
|
||||
done();
|
||||
|
||||
},
|
||||
parseJson: function(json) {}
|
||||
}
|
||||
}
|
||||
remote.getPendingTransactions();
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
792
test/request-test.js
Normal file
792
test/request-test.js
Normal file
@@ -0,0 +1,792 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var Request = utils.load_module('request').Request;
|
||||
var Remote = utils.load_module('remote').Remote;
|
||||
var Server = utils.load_module('server').Server;
|
||||
var Currency = utils.load_module('currency').Currency;
|
||||
|
||||
function makeServer(url) {
|
||||
var server = new Server(new process.EventEmitter(), url);
|
||||
server._connected = true;
|
||||
return server;
|
||||
};
|
||||
|
||||
const SERVER_INFO = {
|
||||
'info': {
|
||||
'build_version': '0.25.2-rc1',
|
||||
'complete_ledgers': '32570-7016339',
|
||||
'hostid': 'LIED',
|
||||
'io_latency_ms': 1,
|
||||
'last_close': {
|
||||
'converge_time_s': 2.013,
|
||||
'proposers': 5
|
||||
},
|
||||
'load_factor': 1,
|
||||
'peers': 42,
|
||||
'pubkey_node': 'n9LpxYuMx4Epz4Wz8Kg2kH3eBTx1mUtHnYwtCdLoj3HC85L2pvBm',
|
||||
'server_state': 'full',
|
||||
'validated_ledger': {
|
||||
'age': 0,
|
||||
'base_fee_xrp': 0.00001,
|
||||
'hash': 'E43FD49087B18031721D9C3C4743FE1692C326AFF7084A2C01B355CE65A4C699',
|
||||
'reserve_base_xrp': 20,
|
||||
'reserve_inc_xrp': 5,
|
||||
'seq': 7016339
|
||||
},
|
||||
'validation_quorum': 3
|
||||
}
|
||||
};
|
||||
|
||||
describe('Request', function() {
|
||||
it('Send request', function(done) {
|
||||
var remote = {
|
||||
request: function(req) {
|
||||
assert(req instanceof Request);
|
||||
assert.strictEqual(typeof req.message, 'object');
|
||||
assert.strictEqual(req.message.command, 'server_info');
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
request.request();
|
||||
|
||||
// Should only request once
|
||||
request.request();
|
||||
});
|
||||
|
||||
it('Broadcast request', function(done) {
|
||||
var servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
|
||||
var requests = 0;
|
||||
|
||||
servers.forEach(function(server, index, arr) {
|
||||
server._request = function(req) {
|
||||
assert(req instanceof Request);
|
||||
assert.strictEqual(typeof req.message, 'object');
|
||||
assert.strictEqual(req.message.command, 'server_info');
|
||||
if (++requests === arr.length) {
|
||||
done();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
request.broadcast();
|
||||
});
|
||||
|
||||
it('Events API', function(done) {
|
||||
var server = makeServer('wss://localhost:5006');
|
||||
|
||||
server._request = function(req) {
|
||||
assert(req instanceof Request);
|
||||
assert.strictEqual(typeof req.message, 'object');
|
||||
assert.strictEqual(req.message.command, 'server_info');
|
||||
req.emit('success', SERVER_INFO);
|
||||
};
|
||||
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = [ server ];
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
request.once('success', function(res) {
|
||||
assert.deepEqual(res, SERVER_INFO);
|
||||
done();
|
||||
});
|
||||
|
||||
request.request();
|
||||
});
|
||||
|
||||
it('Callback API', function(done) {
|
||||
var server = makeServer('wss://localhost:5006');
|
||||
|
||||
server._request = function(req) {
|
||||
assert(req instanceof Request);
|
||||
assert.strictEqual(typeof req.message, 'object');
|
||||
assert.strictEqual(req.message.command, 'server_info');
|
||||
req.emit('success', SERVER_INFO);
|
||||
};
|
||||
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = [ server ];
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
request.callback(function(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(res, SERVER_INFO);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Timeout', function(done) {
|
||||
var server = makeServer('wss://localhost:5006');
|
||||
var successEmited = false;
|
||||
|
||||
server._request = function(req) {
|
||||
assert(req instanceof Request);
|
||||
assert.strictEqual(typeof req.message, 'object');
|
||||
assert.strictEqual(req.message.command, 'server_info');
|
||||
setTimeout(function() {
|
||||
successEmitted = true;
|
||||
req.emit('success', SERVER_INFO);
|
||||
}, 200);
|
||||
};
|
||||
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = [ server ];
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
request.timeout(10, function() {
|
||||
setTimeout(function() {
|
||||
assert(successEmitted);
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
request.callback(function(err, res) {
|
||||
assert(false, 'Callback should not be called');
|
||||
});
|
||||
});
|
||||
|
||||
it('Timeout - satisfied', function(done) {
|
||||
var server = makeServer('wss://localhost:5006');
|
||||
var successEmited = false;
|
||||
|
||||
server._request = function(req) {
|
||||
assert(req instanceof Request);
|
||||
assert.strictEqual(typeof req.message, 'object');
|
||||
assert.strictEqual(req.message.command, 'server_info');
|
||||
setTimeout(function() {
|
||||
successEmitted = true;
|
||||
req.emit('success', SERVER_INFO);
|
||||
}, 200);
|
||||
};
|
||||
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = [ server ];
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
var timedOut = false;
|
||||
|
||||
request.once('timeout', function() {
|
||||
timedOut = true;
|
||||
});
|
||||
|
||||
request.timeout(1000);
|
||||
|
||||
request.callback(function(err, res) {
|
||||
assert(!timedOut);
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(res, SERVER_INFO);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Set server', function(done) {
|
||||
var servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
|
||||
servers[1]._request = function(req) {
|
||||
assert(req instanceof Request);
|
||||
assert.strictEqual(typeof req.message, 'object');
|
||||
assert.strictEqual(req.message.command, 'server_info');
|
||||
done();
|
||||
};
|
||||
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
|
||||
remote.getServer = function() {
|
||||
return servers[0];
|
||||
};
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.setServer(servers[1]);
|
||||
|
||||
assert.strictEqual(request.server, servers[1]);
|
||||
|
||||
request.request();
|
||||
});
|
||||
|
||||
it('Set server - by URL', function(done) {
|
||||
var servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://127.0.0.1:5007')
|
||||
];
|
||||
|
||||
servers[1]._request = function(req) {
|
||||
assert(req instanceof Request);
|
||||
assert.strictEqual(typeof req.message, 'object');
|
||||
assert.strictEqual(req.message.command, 'server_info');
|
||||
done();
|
||||
};
|
||||
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
|
||||
remote.getServer = function() {
|
||||
return servers[0];
|
||||
};
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.setServer('wss://127.0.0.1:5007');
|
||||
|
||||
assert.strictEqual(request.server, servers[1]);
|
||||
|
||||
request.request();
|
||||
});
|
||||
|
||||
it('Set build path', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote.local_signing = false;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.buildPath(true);
|
||||
assert.strictEqual(request.message.build_path, true);
|
||||
});
|
||||
|
||||
it('Remove build path', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote.local_signing = false;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.buildPath(false);
|
||||
assert(!request.message.hasOwnProperty('build_path'));
|
||||
});
|
||||
|
||||
it('Set build path with local signing', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
assert.throws(function() {
|
||||
request.buildPath(true);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it('Set ledger hash', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.ledgerHash('B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE');
|
||||
assert.strictEqual(request.message.ledger_hash, 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE');
|
||||
});
|
||||
|
||||
it('Set ledger index', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.ledgerIndex(7016915);
|
||||
assert.strictEqual(request.message.ledger_index, 7016915);
|
||||
});
|
||||
|
||||
it('Select cached ledger - index', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._ledger_current_index = 1;
|
||||
remote._ledger_hash = 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE';
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.ledgerChoose(true);
|
||||
assert.strictEqual(request.message.ledger_index, 1);
|
||||
});
|
||||
|
||||
it('Select cached ledger - hash', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._ledger_current_index = 1;
|
||||
remote._ledger_hash = 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE';
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.ledgerChoose();
|
||||
assert.strictEqual(request.message.ledger_hash, 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE');
|
||||
assert.strictEqual(request.message.ledger_index, void(0));
|
||||
});
|
||||
|
||||
it('Select ledger - identifier', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.ledgerSelect('validated');
|
||||
assert.strictEqual(request.message.ledger_index, 'validated');
|
||||
assert.strictEqual(request.message.ledger_hash, void(0));
|
||||
});
|
||||
|
||||
it('Select ledger - index', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.ledgerSelect(7016915);
|
||||
assert.strictEqual(request.message.ledger_index, 7016915);
|
||||
assert.strictEqual(request.message.ledger_hash, void(0));
|
||||
});
|
||||
|
||||
it('Select ledger - hash', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.ledgerSelect('B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE');
|
||||
assert.strictEqual(request.message.ledger_hash, 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE');
|
||||
assert.strictEqual(request.message.ledger_index, void(0));
|
||||
});
|
||||
|
||||
it('Select ledger - undefined', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.ledgerSelect();
|
||||
assert.strictEqual(request.message.ledger_hash, void(0));
|
||||
assert.strictEqual(request.message.ledger_index, void(0));
|
||||
request.ledgerSelect(null);
|
||||
assert.strictEqual(request.message.ledger_hash, void(0));
|
||||
assert.strictEqual(request.message.ledger_index, void(0));
|
||||
request.ledgerSelect(NaN);
|
||||
assert.strictEqual(request.message.ledger_hash, void(0));
|
||||
assert.strictEqual(request.message.ledger_index, void(0));
|
||||
});
|
||||
|
||||
it('Set account_root', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.accountRoot('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59');
|
||||
assert.strictEqual(request.message.account_root, 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59');
|
||||
});
|
||||
|
||||
it('Set index', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.index(1);
|
||||
assert.strictEqual(request.message.index, 1);
|
||||
});
|
||||
|
||||
it('Set offer ID', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.offerId('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', 1337);
|
||||
assert.deepEqual(request.message.offer, {
|
||||
account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
|
||||
seq: 1337
|
||||
});
|
||||
});
|
||||
|
||||
it('Set offer index', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.offerIndex(1337);
|
||||
assert.strictEqual(request.message.offer, 1337);
|
||||
});
|
||||
|
||||
it('Set secret', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.secret('mySecret');
|
||||
assert.strictEqual(request.message.secret, 'mySecret');
|
||||
});
|
||||
|
||||
it('Set transaction hash', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.txHash('E08D6E9754025BA2534A78707605E0601F03ACE063687A0CA1BDDACFCD1698C7');
|
||||
assert.strictEqual(request.message.tx_hash, 'E08D6E9754025BA2534A78707605E0601F03ACE063687A0CA1BDDACFCD1698C7');
|
||||
});
|
||||
|
||||
it('Set transaction JSON', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
var txJson = { hash: 'E08D6E9754025BA2534A78707605E0601F03ACE063687A0CA1BDDACFCD1698C7' };
|
||||
request.txJson(txJson);
|
||||
assert.deepEqual(request.message.tx_json, txJson);
|
||||
});
|
||||
|
||||
it('Set transaction blob', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.txBlob('asdf');
|
||||
assert.strictEqual(request.message.tx_blob, 'asdf');
|
||||
});
|
||||
|
||||
it('Set ripple state', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.rippleState('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', 'USD');
|
||||
assert.deepEqual(request.message.ripple_state, {
|
||||
currency: 'USD',
|
||||
accounts: [ 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59' ]
|
||||
});
|
||||
});
|
||||
|
||||
it('Set accounts', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
request.accounts([
|
||||
'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun',
|
||||
'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
]);
|
||||
|
||||
assert.deepEqual(request.message.accounts, [
|
||||
'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun',
|
||||
'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
]);
|
||||
});
|
||||
|
||||
it('Set accounts - string', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
request.accounts('rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun');
|
||||
|
||||
assert.deepEqual(request.message.accounts, [
|
||||
'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun'
|
||||
]);
|
||||
});
|
||||
|
||||
it('Set accounts proposed', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
request.accountsProposed([
|
||||
'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun',
|
||||
'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
]);
|
||||
|
||||
assert.deepEqual(request.message.accounts_proposed, [
|
||||
'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun',
|
||||
'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
]);
|
||||
});
|
||||
|
||||
it('Add account', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
request.accounts([
|
||||
'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun',
|
||||
]);
|
||||
|
||||
request.addAccount('rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B');
|
||||
|
||||
assert.deepEqual(request.message.accounts, [
|
||||
'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun',
|
||||
'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
]);
|
||||
});
|
||||
|
||||
it('Add account proposed', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
request.accountsProposed([
|
||||
'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun',
|
||||
]);
|
||||
|
||||
request.addAccountProposed('rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B');
|
||||
|
||||
assert.deepEqual(request.message.accounts_proposed, [
|
||||
'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun',
|
||||
'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
]);
|
||||
});
|
||||
|
||||
it('Set books', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
var books = [
|
||||
{
|
||||
'taker_gets': {
|
||||
'currency': 'EUR',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': 'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
request.books(books);
|
||||
|
||||
assert.deepEqual(request.message.books, [
|
||||
{
|
||||
'taker_gets': {
|
||||
'currency': Currency.from_json('EUR').to_hex(),
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': Currency.from_json('USD').to_hex(),
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'snapshot': true
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('Add book', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
request.addBook({
|
||||
'taker_gets': {
|
||||
'currency': 'CNY',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': 'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
}
|
||||
});
|
||||
|
||||
assert.deepEqual(request.message.books, [
|
||||
{
|
||||
'taker_gets': {
|
||||
'currency': Currency.from_json('CNY').to_hex(),
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': Currency.from_json('USD').to_hex(),
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'snapshot': true
|
||||
}
|
||||
]);
|
||||
|
||||
var books = [
|
||||
{
|
||||
'taker_gets': {
|
||||
'currency': 'EUR',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': 'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
request.books(books);
|
||||
|
||||
assert.deepEqual(request.message.books, [
|
||||
{
|
||||
'taker_gets': {
|
||||
'currency': '0000000000000000000000004555520000000000', // EUR hex
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': '0000000000000000000000005553440000000000', // USD hex
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'snapshot': true
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('Add book - missing side', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
request.message.books = void(0);
|
||||
|
||||
var books = [
|
||||
{
|
||||
'taker_gets': {
|
||||
'currency': 'EUR',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
assert.throws(function() {
|
||||
request.books(books);
|
||||
});
|
||||
});
|
||||
|
||||
it('Add book - without snapshot', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
request.message.books = void(0);
|
||||
|
||||
var book = {
|
||||
'taker_gets': {
|
||||
'currency': 'EUR',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': 'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'both': true
|
||||
};
|
||||
|
||||
request.addBook(book, true);
|
||||
|
||||
assert.deepEqual(request.message.books, [{
|
||||
'taker_gets': {
|
||||
'currency': Currency.from_json('EUR').to_hex(),
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': Currency.from_json('USD').to_hex(),
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'both': true,
|
||||
'snapshot': true
|
||||
}]);
|
||||
});
|
||||
|
||||
it('Add book - no snapshot', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'server_info');
|
||||
|
||||
request.message.books = void(0);
|
||||
|
||||
var book = {
|
||||
'taker_gets': {
|
||||
'currency': 'EUR',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': 'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'both': true
|
||||
};
|
||||
|
||||
request.addBook(book, false);
|
||||
|
||||
assert.deepEqual(request.message.books, [{
|
||||
'taker_gets': {
|
||||
'currency': Currency.from_json('EUR').to_hex(),
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': Currency.from_json('USD').to_hex(),
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'both': true
|
||||
}]);
|
||||
});
|
||||
|
||||
it('Add stream', function() {
|
||||
var remote = new Remote();
|
||||
remote._connected = true;
|
||||
|
||||
var request = new Request(remote, 'subscribe');
|
||||
|
||||
request.addStream('server', 'ledger');
|
||||
request.addStream('transactions', 'transactions_proposed');
|
||||
request.addStream('accounts', [ 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' ]);
|
||||
request.addStream('accounts_proposed', [ 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59' ]);
|
||||
request.addStream('books', [{
|
||||
'taker_gets': {
|
||||
'currency': 'EUR',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': 'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
}
|
||||
}]);
|
||||
|
||||
assert.deepEqual(request.message, {
|
||||
'command': 'subscribe',
|
||||
'id': void(0),
|
||||
'streams': [
|
||||
'server',
|
||||
'ledger',
|
||||
'transactions',
|
||||
'transactions_proposed',
|
||||
'accounts',
|
||||
'accounts_proposed',
|
||||
'books'
|
||||
],
|
||||
'accounts': [
|
||||
'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
],
|
||||
'accounts_proposed': [
|
||||
'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59'
|
||||
],
|
||||
'books': [
|
||||
{
|
||||
'taker_gets': {
|
||||
'currency': '0000000000000000000000004555520000000000',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': '0000000000000000000000005553440000000000',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'snapshot': true
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
75
test/seed-test.js
Normal file
75
test/seed-test.js
Normal file
@@ -0,0 +1,75 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var Seed = utils.load_module('seed').Seed;
|
||||
var config = require('./testutils').get_config();
|
||||
|
||||
describe('Seed', function() {
|
||||
it('can generate many addresses', function () {
|
||||
|
||||
var test_data = [
|
||||
// Format:
|
||||
// [passphrase, address, nth-for-seed, expected-public-key]
|
||||
["masterpassphrase", "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", 0,
|
||||
"0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020"],
|
||||
["masterpassphrase", "r4bYF7SLUMD7QgSLLpgJx38WJSY12ViRjP", 1,
|
||||
"02CD8C4CE87F86AAD1D9D18B03DE28E6E756F040BD72A9C127862833EB90D60BAD"],
|
||||
["masterpassphrase", "rLpAd4peHUMBPbVJASMYK5GTBUSwXRD9nx", 2,
|
||||
"0259A57642A6F4AEFC9B8062AF453FDEEEAC5572BA602BB1DBD5EF011394C6F9FC"],
|
||||
["otherpassphrase", "rpe3YWSVwGU2PmUzebAPg2deBXHtmba7hJ", 0,
|
||||
"022235A3DB2CAE57C60B7831929611D58867F86D28C0AD3C82473CC4A84990D01B"],
|
||||
["otherpassphrase", "raAPC2gALSmsTkXR4wUwQcPgX66kJuLv2S", 5,
|
||||
"03F0619AFABE08D22D98C8721895FE3673B6174168949976F2573CE1138C124994"],
|
||||
["yetanotherpassphrase", "rKnM44fS48qrGiDxB5fB5u64vHVJwjDPUo", 0,
|
||||
"0385AD049327EF7E5EC429350A15CEB23955037DE99660F6E70C11C5ABF4407036"],
|
||||
["yetanotherpassphrase", "rMvkT1RHPfsZwTFbKDKBEisa5U4d2a9V8n", 1,
|
||||
"023A2876EA130CBE7BBA0573C2DB4C4CEB9A7547666915BD40366CDC6150CF54DC"]
|
||||
];
|
||||
|
||||
function assert_helper(seed_json, address_or_nth, expected) {
|
||||
var seed = Seed.from_json(seed_json);
|
||||
var keypair = seed.get_key(address_or_nth, 500);
|
||||
assert.strictEqual(keypair.to_hex_pub(), expected);
|
||||
}
|
||||
|
||||
for (var nth = 0; nth < test_data.length; nth++) {
|
||||
var seed_json = test_data[nth][0];
|
||||
var address = test_data[nth][1];
|
||||
var nth_for_seed = test_data[nth][2];
|
||||
var expected = test_data[nth][3];
|
||||
|
||||
//`seed.get_key($ripple_address)` is arguably an ill concieved feature
|
||||
// as it needs to generate `nth` many keypairs and generate hashed public
|
||||
// keys (addresses) for equality tests ??
|
||||
// Would need remote.set_secret(address, private_key_not_seed) ??
|
||||
assert_helper(seed_json, address, expected);
|
||||
|
||||
// This isn't too bad as it only needs to generate one keypair `seq`
|
||||
assert_helper(seed_json, nth_for_seed, expected);
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
it('should return the key_pair for a valid account and secret pair', function() {
|
||||
var address = 'r3GgMwvgvP8h4yVWvjH1dPZNvC37TjzBBE';
|
||||
var seed = Seed.from_json('shsWGZcmZz6YsWWmcnpfr6fLTdtFV');
|
||||
var keyPair = seed.get_key(address);
|
||||
assert.strictEqual(keyPair.get_address().to_json(), address);
|
||||
assert.strictEqual(keyPair.to_hex_pub(), '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8');
|
||||
});
|
||||
|
||||
it('should not find a KeyPair for a secret that does not belong to the given account', function() {
|
||||
var address = 'r3GgMwvgvP8h4yVWvjH1dPZNvC37TjzBBE';
|
||||
var secret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb';
|
||||
var seed = Seed.from_json('snoPBrXtMeMyMHUVTgbuqAfg1SUTb');
|
||||
try {
|
||||
seed.get_key(address);
|
||||
assert(false, 'should throw an error');
|
||||
} catch(e) {
|
||||
assert.strictEqual(e.message, 'Too many loops looking for KeyPair yielding '+address+' from '+secret);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
94
test/serializedobject-test.js
Normal file
94
test/serializedobject-test.js
Normal file
@@ -0,0 +1,94 @@
|
||||
var utils = require('./testutils');
|
||||
var assert = require('assert');
|
||||
var SerializedObject = utils.load_module('serializedobject').SerializedObject;
|
||||
|
||||
describe('Serialized object', function() {
|
||||
describe('#from_json(v).to_json() == v', function(){
|
||||
it('outputs same as passed to from_json', function() {
|
||||
var input_json = {
|
||||
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Amount: '274579388',
|
||||
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Fee: '15',
|
||||
Flags: 0,
|
||||
Paths: [[
|
||||
{
|
||||
account: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV',
|
||||
currency: 'USD',
|
||||
issuer: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV'
|
||||
},
|
||||
{
|
||||
currency: 'XRP'
|
||||
}
|
||||
]],
|
||||
SendMax: {
|
||||
currency: 'USD',
|
||||
issuer: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
value: '2.74579388'
|
||||
},
|
||||
Sequence: 351,
|
||||
SigningPubKey: '02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A',
|
||||
TransactionType: 'Payment',
|
||||
TxnSignature: '30450221009DA3A42DD25E3B22EC45AD8BA8FC7A954264264A816D300B2DF69F814D7D4DD2022072C9627F97EEC6DA13DE841E06E2CD985EF06A0FBB15DDBF0800D0730C8986BF'
|
||||
};
|
||||
var output_json = SerializedObject.from_json(input_json).to_json();
|
||||
assert.deepEqual(input_json, output_json);
|
||||
});
|
||||
});
|
||||
describe('#from_json', function() {
|
||||
it('understands TransactionType as a Number', function() {
|
||||
var input_json = {
|
||||
// no non required fields
|
||||
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Amount: '274579388',
|
||||
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Fee: '15',
|
||||
Sequence: 351,
|
||||
SigningPubKey: '02',// VL field ;)
|
||||
TransactionType: 0 //
|
||||
};
|
||||
var output_json = SerializedObject.from_json(input_json).to_json();
|
||||
|
||||
assert.equal(0, input_json.TransactionType);
|
||||
assert.equal("Payment", output_json.TransactionType);
|
||||
});
|
||||
it('understands LedgerEntryType as a Number', function() {
|
||||
var input_json = {
|
||||
// no, non required fields
|
||||
"LedgerEntryType": 100,
|
||||
"Flags": 0,
|
||||
"Indexes": [],
|
||||
"RootIndex": "000360186E008422E06B72D5B275E29EE3BE9D87A370F424E0E7BF613C465909"
|
||||
}
|
||||
|
||||
var output_json = SerializedObject.from_json(input_json).to_json();
|
||||
assert.equal(100, input_json.LedgerEntryType);
|
||||
assert.equal("DirectoryNode", output_json.LedgerEntryType);
|
||||
});
|
||||
describe('Format validation', function() {
|
||||
// Peercover actually had a problem submitting transactions without a `Fee`
|
||||
// and rippled was only informing of "transaction is invalid"
|
||||
it('should throw an Error when there is a missing field', function() {
|
||||
var input_json = {
|
||||
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Amount: '274579388',
|
||||
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Sequence: 351,
|
||||
SigningPubKey: '02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A',
|
||||
TransactionType: 'Payment',
|
||||
TxnSignature: '30450221009DA3A42DD25E3B22EC45AD8BA8FC7A954264264A816D300B2DF69F814D7D4DD2022072C9627F97EEC6DA13DE841E06E2CD985EF06A0FBB15DDBF0800D0730C8986BF'
|
||||
};
|
||||
assert.throws (
|
||||
function() {
|
||||
var output_json = SerializedObject.from_json(input_json);
|
||||
},
|
||||
/Payment is missing fields: \["Fee"\]/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
File diff suppressed because it is too large
Load Diff
1141
test/server-test.js
Normal file
1141
test/server-test.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,30 +1,57 @@
|
||||
var buster = require("buster");
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var Seed = utils.load_module('seed').Seed;
|
||||
|
||||
var Seed = require("../src/js/ripple/seed").Seed;
|
||||
|
||||
try {
|
||||
var conf = require('./config');
|
||||
} catch(exception) {
|
||||
var conf = require('./config-example');
|
||||
function _isNaN(n) {
|
||||
return typeof n === 'number' && isNaN(n);
|
||||
}
|
||||
|
||||
var config = require('../src/js/ripple/config').load(conf);
|
||||
|
||||
buster.testCase("Signing", {
|
||||
"Keys" : {
|
||||
"SigningPubKey 1 (ripple-client issue #245)" : function () {
|
||||
var seed = Seed.from_json("saESc82Vun7Ta5EJRzGJbrXb5HNYk");
|
||||
var key = seed.get_key("rBZ4j6MsoctipM6GEyHSjQKzXG3yambDnZ");
|
||||
describe('Signing', function() {
|
||||
describe('Keys', function() {
|
||||
it('SigningPubKey 1 (ripple-client issue #245)', function () {
|
||||
var seed = Seed.from_json('saESc82Vun7Ta5EJRzGJbrXb5HNYk');
|
||||
var key = seed.get_key('rBZ4j6MsoctipM6GEyHSjQKzXG3yambDnZ');
|
||||
var pub = key.to_hex_pub();
|
||||
buster.assert.equals(pub, "0396941B22791A448E5877A44CE98434DB217D6FB97D63F0DAD23BE49ED45173C9");
|
||||
},
|
||||
"SigningPubKey 2 (master seed)" : function () {
|
||||
var seed = Seed.from_json("snoPBrXtMeMyMHUVTgbuqAfg1SUTb");
|
||||
var key = seed.get_key("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh");
|
||||
assert.strictEqual(pub, '0396941B22791A448E5877A44CE98434DB217D6FB97D63F0DAD23BE49ED45173C9');
|
||||
});
|
||||
it('SigningPubKey 2 (master seed)', function () {
|
||||
var seed = Seed.from_json('snoPBrXtMeMyMHUVTgbuqAfg1SUTb');
|
||||
var key = seed.get_key('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
var pub = key.to_hex_pub();
|
||||
buster.assert.equals(pub, "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020");
|
||||
}
|
||||
}
|
||||
assert.strictEqual(pub, '0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020');
|
||||
});
|
||||
});
|
||||
describe('parse_json', function() {
|
||||
it('empty string', function() {
|
||||
assert(_isNaN(new Seed().parse_json('').to_json()));
|
||||
});
|
||||
it('hex string', function() {
|
||||
// 32 0s is a valid hex repr of seed bytes
|
||||
var str = new Array(33).join('0');
|
||||
assert.strictEqual((new Seed().parse_json(str).to_json()), 'sp6JS7f14BuwFY8Mw6bTtLKWauoUs');
|
||||
});
|
||||
it('passphrase', function() {
|
||||
var str = new Array(60).join('0');
|
||||
assert.strictEqual('snFRPnVL3secohdpwSie8ANXdFQvG', new Seed().parse_json(str).to_json());
|
||||
});
|
||||
it('null', function() {
|
||||
assert(_isNaN(new Seed().parse_json(null).to_json()));
|
||||
});
|
||||
});
|
||||
describe('parse_passphrase', function() {
|
||||
it('invalid passphrase', function() {
|
||||
assert.throws(function() {
|
||||
new Seed().parse_passphrase(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('get_key', function() {
|
||||
it('get key from invalid seed', function() {
|
||||
assert.throws(function() {
|
||||
new Seed().get_key('rBZ4j6MsoctipM6GEyHSjQKzXG3yambDnZ');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
28
test/sjcl-ecdsa-canonical-test.js
Normal file
28
test/sjcl-ecdsa-canonical-test.js
Normal file
@@ -0,0 +1,28 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var sjcl = require('../build/sjcl');
|
||||
var Seed = require('../src/js/ripple/seed').Seed;
|
||||
|
||||
describe('SJCL ECDSA Canonicalization', function() {
|
||||
describe('canonicalizeSignature', function() {
|
||||
it('should canonicalize non-canonical signatures', function () {
|
||||
var seed = Seed.from_json('saESc82Vun7Ta5EJRzGJbrXb5HNYk');
|
||||
var key = seed.get_key('rBZ4j6MsoctipM6GEyHSjQKzXG3yambDnZ');
|
||||
|
||||
var rs = sjcl.codec.hex.toBits("27ce1b914045ba7e8c11a2f2882cb6e07a19d4017513f12e3e363d71dc3fff0fb0a0747ecc7b4ca46e45b3b32b6b2a066aa0249c027ef11e5bce93dab756549c");
|
||||
rs = sjcl.ecc.ecdsa.secretKey.prototype.canonicalizeSignature.call(key._secret, rs);
|
||||
assert.strictEqual(sjcl.codec.hex.fromBits(rs), "27ce1b914045ba7e8c11a2f2882cb6e07a19d4017513f12e3e363d71dc3fff0f4f5f8b813384b35b91ba4c4cd494d5f8500eb84aacc9af1d6403cab218dfeca5");
|
||||
});
|
||||
|
||||
it('should not touch canonical signatures', function () {
|
||||
var seed = Seed.from_json('saESc82Vun7Ta5EJRzGJbrXb5HNYk');
|
||||
var key = seed.get_key('rBZ4j6MsoctipM6GEyHSjQKzXG3yambDnZ');
|
||||
|
||||
var rs = sjcl.codec.hex.toBits("5c32bc2b4d34e27af9fb66eeea0f47f6afb3d433658af0f649ebae7b872471ab7d23860688aaf9d8131f84cfffa6c56bf9c32fd8b315b2ef9d6bcb243f7a686c");
|
||||
rs = sjcl.ecc.ecdsa.secretKey.prototype.canonicalizeSignature.call(key._secret, rs);
|
||||
assert.strictEqual(sjcl.codec.hex.fromBits(rs), "5c32bc2b4d34e27af9fb66eeea0f47f6afb3d433658af0f649ebae7b872471ab7d23860688aaf9d8131f84cfffa6c56bf9c32fd8b315b2ef9d6bcb243f7a686c");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
245
test/sjcl-ecdsa-recoverablepublickey-test.js
Normal file
245
test/sjcl-ecdsa-recoverablepublickey-test.js
Normal file
@@ -0,0 +1,245 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var sjcl = require('../build/sjcl');
|
||||
|
||||
describe('ECDSA signing with recoverable public key', function(){
|
||||
|
||||
describe('Sign and recover public key from signature', function(){
|
||||
|
||||
it('should recover public keys from signatures it generates', function(){
|
||||
|
||||
var messages = [{
|
||||
message: 'Hello world!',
|
||||
secret_hex: '9931c08f61f127d5735fa3c60e702212ce7ed9a2ac90d5dbade99c689728cd9b',
|
||||
random_value: '5473a3dbdc13ec9efbad7f7f929fbbea404af556a48041dd9d41d29fdbc989ad',
|
||||
hash_function: sjcl.hash.sha512.hash
|
||||
// signature: 'AAAAGzFa1pYjhssCpDFZgFSnYQ8qCnMkLaZrg0mXZyNQ2NxgMQ8z9U3ngYerxSZCEt3Q4raMIpt03db7jDNGbfmHy8I='
|
||||
}, {
|
||||
// Correct recovery value for this one is 0
|
||||
message: 'ua5pdcG0I1JuhSr9Fwai2UoZ9ll5leUtHE5NzSSNnPkw8nSPH5mT1gE1fe0sn',
|
||||
secret_hex: '84814318ffe6e612694ad59b9084b7b66d68b6979567c619171a67b05e2b654b',
|
||||
random_value: '14261d30b319709c10ab13cabe595313b99dd2d5c76b8b38d7eb445f0b81cc9a',
|
||||
hash_function: sjcl.hash.sha512.hash
|
||||
// signature: 'AAAAHGjpBM7wnTHbPGo0TXsxKbr+d7KvACuJ/eGQsp3ZJfOOQHszaciRo3ClenwKixcquFcBlaVfHlOc3JWOZq1RjpQ='
|
||||
}, {
|
||||
// Correct recovery value for this one is 1
|
||||
message: 'rxc76UnmVTp',
|
||||
secret_hex: '37eac47c212be8ea8372f506b11673c281cd9ea29a035c2c9e90d027c3dbecc6',
|
||||
random_value: '61b53ca6de0543f911765ae216a3a4d851918a0733fba9ac80cf29de5bec8032',
|
||||
hash_function: sjcl.hash.sha256.hash
|
||||
// signature: 'AAAAG8L/yOA3nNqK4aOiQWJmOaWvkvr3NoTk6wCdX97U3qowdgFd98UK3evWV16qO3RHgFMEnUW/Vt4+kcidqW6hMo0='
|
||||
}];
|
||||
|
||||
var curve = sjcl.ecc.curves['c256'];
|
||||
|
||||
for (var m = 0; m < messages.length; m++) {
|
||||
|
||||
var message = messages[m].message;
|
||||
var secret_hex = messages[m].secret_hex;
|
||||
var random_value = messages[m].random_value;
|
||||
var hash_function = messages[m].hash_function;
|
||||
|
||||
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
|
||||
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
|
||||
|
||||
var pub_val_point = secret_key._curve.G.mult(secret_key._exponent);
|
||||
var public_key = new sjcl.ecc.ecdsa.publicKey(curve, pub_val_point);
|
||||
var hash = hash_function(message);
|
||||
|
||||
var recoverable_signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value);
|
||||
var recovered_public_key = sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, recoverable_signature);
|
||||
|
||||
assert.deepEqual(public_key.get().x, recovered_public_key.get().x, 'The x value for the recovered public key did not match for message: ' + message + '. Expected: ' + public_key.get().x.toString() + '. Actual: ' + recovered_public_key.get().x.toString());
|
||||
assert.deepEqual(public_key.get().y, recovered_public_key.get().y, 'The y value for the recovered public key did not match for message: ' + message + '. Expected: ' + public_key.get().y.toString() + '. Actual: ' + recovered_public_key.get().y.toString());
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('signWithRecoverablePublicKey', function(){
|
||||
|
||||
// it('should produce the same values as bitcoinjs-lib\'s implementation', function(){
|
||||
|
||||
// // TODO: figure out why bitcoinjs-lib and this produce different signature values
|
||||
|
||||
// var curve = sjcl.ecc.curves['c256'];
|
||||
|
||||
// var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
|
||||
// var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
|
||||
// var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
|
||||
|
||||
// // var public_key = '0217b9f5b3ba8d550f19fdfb5233818cd27d19aaea029b667f547f5918c307ed3b';
|
||||
// var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a';
|
||||
// var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778');
|
||||
|
||||
// var bitcoin_signature_base64 = 'IJPzXewhO1CORRx14FROzZC8ne4v0Me94UZoBKH15e4pcSgeYiYeKZ4PJOBI/D5yqUOhemO+rKKHhE0HL66kAcM=';
|
||||
|
||||
// var signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value);
|
||||
// var signature_base64 = sjcl.codec.base64.fromBits(signature);
|
||||
|
||||
// assert.equal(signature_base64, bitcoin_signature_base64);
|
||||
|
||||
// });
|
||||
|
||||
it('should produce an error if the hash is not given as a bitArray', function(){
|
||||
|
||||
var curve = sjcl.ecc.curves['c256'];
|
||||
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
|
||||
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
|
||||
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
|
||||
var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a';
|
||||
var hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778';
|
||||
|
||||
assert.throws(function(){
|
||||
secret_key.signWithRecoverablePublicKey(hash, 0, random_value);
|
||||
}, /(?=.*hash)(?=.*bitArray).+/);
|
||||
|
||||
});
|
||||
|
||||
it('should return a bitArray', function(){
|
||||
|
||||
var curve = sjcl.ecc.curves['c256'];
|
||||
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
|
||||
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
|
||||
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
|
||||
var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a';
|
||||
var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778');
|
||||
|
||||
var signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value);
|
||||
assert(typeof signature === 'object' && signature.length > 0 && typeof signature[0] === 'number');
|
||||
|
||||
});
|
||||
|
||||
it('should return a bitArray where the first word contains the recovery factor', function(){
|
||||
|
||||
var curve = sjcl.ecc.curves['c256'];
|
||||
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
|
||||
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
|
||||
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
|
||||
var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a';
|
||||
var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778');
|
||||
|
||||
var signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value);
|
||||
var recovery_factor = signature[0] - 27;
|
||||
|
||||
assert(recovery_factor >= 0 && recovery_factor < 4);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('recoverFromSignature', function(){
|
||||
|
||||
// it('should be able to recover public keys from bitcoinjs-lib\'s implementation', function(){
|
||||
|
||||
// // TODO: figure out why bitcoinjs-lib and this produce different signature values
|
||||
|
||||
// var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778');
|
||||
// var signature = sjcl.codec.base64.toBits('IJPzXewhO1CORRx14FROzZC8ne4v0Me94UZoBKH15e4pcSgeYiYeKZ4PJOBI/D5yqUOhemO+rKKHhE0HL66kAcM=');
|
||||
|
||||
// var public_key = sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature);
|
||||
|
||||
// });
|
||||
|
||||
it('should produce an error if the signature given does not have the recovery factor prefix', function(){
|
||||
|
||||
var curve = sjcl.ecc.curves['c256'];
|
||||
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
|
||||
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
|
||||
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
|
||||
var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a';
|
||||
var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778');
|
||||
|
||||
var signature = secret_key.sign(hash, 0, random_value);
|
||||
|
||||
assert.throws(function(){
|
||||
sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature);
|
||||
}, /(?=.*signature)(?=.*recovery factor)(?=.*public key).*/);
|
||||
|
||||
});
|
||||
|
||||
it('should produce an error if it is not given both the hash and the signature', function(){
|
||||
|
||||
var curve = sjcl.ecc.curves['c256'];
|
||||
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
|
||||
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
|
||||
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
|
||||
var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a';
|
||||
var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778');
|
||||
|
||||
var signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value);
|
||||
|
||||
assert.throws(function(){
|
||||
sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash);
|
||||
}, /(?=.*hash\ and\ signature)(?=.*recover\ public\ key).*/);
|
||||
|
||||
assert.throws(function(){
|
||||
sjcl.ecc.ecdsa.publicKey.recoverFromSignature(signature);
|
||||
}, /(?=.*hash\ and\ signature)(?=.*recover\ public\ key).*/);
|
||||
|
||||
});
|
||||
|
||||
it('should produce an error if it cannot generate a valid public key from the the signature', function(){
|
||||
|
||||
var curve = sjcl.ecc.curves['c256'];
|
||||
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
|
||||
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
|
||||
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
|
||||
var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a';
|
||||
var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778');
|
||||
|
||||
var signature = sjcl.codec.base64.toBits('IJPzXewhO1CORRx14FROzZC8ne4v0Me94UZoBKH15e4pcSgeYiYeKZ4PJOBI/D5yqUOhemO+rKKHhE0HL66kAcM=');
|
||||
signature[0] = 27;
|
||||
|
||||
signature[3] = 0 - signature[3];
|
||||
|
||||
assert.throws(function(){
|
||||
sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature);
|
||||
}, /(?=.*Cannot\ recover\ public\ key).*/);
|
||||
|
||||
});
|
||||
|
||||
it('should return a publicKey object', function(){
|
||||
|
||||
var curve = sjcl.ecc.curves['c256'];
|
||||
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
|
||||
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
|
||||
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
|
||||
var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a';
|
||||
var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778');
|
||||
var signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value);
|
||||
|
||||
var key = sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature);
|
||||
|
||||
assert(key instanceof sjcl.ecc.ecdsa.publicKey);
|
||||
|
||||
});
|
||||
|
||||
it('tampering with the signature should produce a different public key, if it produces a valid one at all', function(){
|
||||
|
||||
var curve = sjcl.ecc.curves['c256'];
|
||||
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
|
||||
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
|
||||
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
|
||||
var random_value = 'c3aa71cecb965bbbc96083d868b4955d77adb4e02ce229fe60869f745dfcd4e4a4d0f17a15a353d7592dca1baba2824e45c8e7a8f9faad3ce2c2d3792799f27a';
|
||||
var hash = sjcl.codec.hex.toBits('e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778');
|
||||
|
||||
var signature = secret_key.signWithRecoverablePublicKey(hash, 0, random_value);
|
||||
|
||||
signature[3]++;
|
||||
|
||||
var original_public_key = new sjcl.ecc.ecdsa.publicKey(curve, curve.G.mult(secret_key._exponent));
|
||||
var recovered_public_key = sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature);
|
||||
|
||||
assert.notDeepEqual(original_public_key.get().x, recovered_public_key.get().x);
|
||||
assert.notDeepEqual(original_public_key.get().y, recovered_public_key.get().y);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
52
test/sjcl-extramath-test.js
Normal file
52
test/sjcl-extramath-test.js
Normal file
@@ -0,0 +1,52 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var sjcl = require('../build/sjcl');
|
||||
|
||||
describe('SJCL Extramath', function() {
|
||||
describe('setBitM', function() {
|
||||
it('0x0f set bit 4 => 0x1f', function () {
|
||||
var val = new sjcl.bn("0f");
|
||||
val.setBitM(4);
|
||||
assert.strictEqual(val.toString(), '0x1f');
|
||||
});
|
||||
it('0x0f set bit 23 => 0x80000f', function () {
|
||||
var val = new sjcl.bn("0f");
|
||||
val.setBitM(23);
|
||||
assert.strictEqual(val.toString(), '0x80000f');
|
||||
});
|
||||
it('0x0f set bit 24 => 0x100000f', function () {
|
||||
var val = new sjcl.bn("0f");
|
||||
val.setBitM(24);
|
||||
assert.strictEqual(val.toString(), '0x100000f');
|
||||
});
|
||||
});
|
||||
describe('testBit', function() {
|
||||
it('0x03', function () {
|
||||
var val = new sjcl.bn("03");
|
||||
assert.strictEqual(val.testBit(0), 1);
|
||||
assert.strictEqual(val.testBit(1), 1);
|
||||
assert.strictEqual(val.testBit(2), 0);
|
||||
});
|
||||
it('0x1000000', function () {
|
||||
var val = new sjcl.bn("1000000");
|
||||
assert.strictEqual(val.testBit(25), 0);
|
||||
assert.strictEqual(val.testBit(24), 1);
|
||||
assert.strictEqual(val.testBit(23), 0);
|
||||
assert.strictEqual(val.testBit( 1), 0);
|
||||
assert.strictEqual(val.testBit( 0), 0);
|
||||
});
|
||||
it('0xff7fffffff', function () {
|
||||
var val = new sjcl.bn("ff7fffffff");
|
||||
assert.strictEqual(val.testBit(32), 1);
|
||||
assert.strictEqual(val.testBit(31), 0);
|
||||
assert.strictEqual(val.testBit(30), 1);
|
||||
assert.strictEqual(val.testBit(24), 1);
|
||||
assert.strictEqual(val.testBit(23), 1);
|
||||
assert.strictEqual(val.testBit(22), 1);
|
||||
assert.strictEqual(val.testBit( 1), 1);
|
||||
assert.strictEqual(val.testBit( 0), 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
48
test/sjcl-jacobi-test.js
Normal file
48
test/sjcl-jacobi-test.js
Normal file
@@ -0,0 +1,48 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var sjcl = require('../build/sjcl');
|
||||
|
||||
describe('SJCL Jacobi', function() {
|
||||
it('(15/13) = -1', function () {
|
||||
var jac = new sjcl.bn(15).jacobi(13);
|
||||
assert.strictEqual(jac, -1);
|
||||
});
|
||||
it('(4/97) = 1', function () {
|
||||
var jac = new sjcl.bn(4).jacobi(97);
|
||||
assert.strictEqual(jac, 1);
|
||||
});
|
||||
it('(17/17) = 0', function () {
|
||||
var jac = new sjcl.bn(17).jacobi(17);
|
||||
assert.strictEqual(jac, 0);
|
||||
});
|
||||
it('(34/17) = 0', function () {
|
||||
var jac = new sjcl.bn(34).jacobi(17);
|
||||
assert.strictEqual(jac, 0);
|
||||
});
|
||||
it('(19/45) = 1', function () {
|
||||
var jac = new sjcl.bn(19).jacobi(45);
|
||||
assert.strictEqual(jac, 1);
|
||||
});
|
||||
it('(8/21) = -1', function () {
|
||||
var jac = new sjcl.bn(8).jacobi(21);
|
||||
assert.strictEqual(jac, -1);
|
||||
});
|
||||
it('(5/21) = 1', function () {
|
||||
var jac = new sjcl.bn(5).jacobi(21);
|
||||
assert.strictEqual(jac, 1);
|
||||
});
|
||||
it('(1001/9907) = -1', function () {
|
||||
var jac = new sjcl.bn(1001).jacobi(9907);
|
||||
assert.strictEqual(jac, -1);
|
||||
});
|
||||
it('(1236/20003) = 1', function () {
|
||||
var jac = new sjcl.bn(1236).jacobi(20003);
|
||||
assert.strictEqual(jac, 1);
|
||||
});
|
||||
it('With huge numbers', function () {
|
||||
var jac = new sjcl.bn("217033ffbc5a462201407027104916c5e7bf09f2b0c926f7c5cb20858be29d92e7fe67080eeb268fcbc2bc44d9cecfe1d3acbb302111eba355a8b769ed4bdbf773d37dca47e2293c173ff4f84b38f4e84bfad7cc1a913d70e11cf664a95575b80ec9d10123289b402ad2c71c70f2dc28360262d3d703faa964c741a711e4eebd324d659601dd14564fcd8c5908bbf8c97cf3ff82f083da3005848b48cb545b31be2039cca1d67714f32d32b3228c1a659415ee6c138ca274f789006a90a9a41bbc3934b84c78948eae8351f45696fa716b1328561f4c3bbb44ac73112c291b6b4587365e44fa09d583fb8074eb35bf947231e500c0d2c79c5fb957e50e84f6c9").jacobi("c7f1bc1dfb1be82d244aef01228c1409c198894eca9e21430f1669b4aa3864c9f37f3d51b2b4ba1ab9e80f59d267fda1521e88b05117993175e004543c6e3611242f24432ce8efa3b81f0ff660b4f91c5d52f2511a6f38181a7bf9abeef72db056508bbb4eeb5f65f161dd2d5b439655d2ae7081fcc62fdcb281520911d96700c85cdaf12e7d1f15b55ade867240722425198d4ce39019550c4c8a921fc231d3e94297688c2d77cd68ee8fdeda38b7f9a274701fef23b4eaa6c1a9c15b2d77f37634930386fc20ec291be95aed9956801e1c76601b09c413ad915ff03bfdc0b6b233686ae59e8caf11750b509ab4e57ee09202239baee3d6e392d1640185e1cd");
|
||||
assert.strictEqual(jac, -1);
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
88
test/sjcl-montgomery.js
Normal file
88
test/sjcl-montgomery.js
Normal file
@@ -0,0 +1,88 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var sjcl = require('../build/sjcl');
|
||||
|
||||
function testExp(vec) {
|
||||
var actual = new sjcl.bn(vec.g).powermodMontgomery(new sjcl.bn(vec.e),
|
||||
new sjcl.bn(vec.m));
|
||||
assert.strictEqual(actual.toString(), new sjcl.bn(vec.r).toString());
|
||||
}
|
||||
|
||||
describe('SJCL Montgomery Exponentiation', function() {
|
||||
describe('powermod', function() {
|
||||
it('test 1', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: 3,
|
||||
m: 3,
|
||||
r: 2
|
||||
});
|
||||
});
|
||||
it('test 2', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: "10000000000000000000000000",
|
||||
m: 1337,
|
||||
r: 1206
|
||||
});
|
||||
});
|
||||
it('test 3', function () {
|
||||
testExp({
|
||||
g: 17,
|
||||
e: 90,
|
||||
m: 34717861147,
|
||||
r: 28445204336
|
||||
});
|
||||
});
|
||||
it('test 4', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: "0x844A000000000000000000000",
|
||||
m: 13,
|
||||
r: 9
|
||||
});
|
||||
});
|
||||
it('test 5', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: 0x1010,
|
||||
m: 131,
|
||||
r: 59
|
||||
});
|
||||
});
|
||||
it('test 6', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: "43207437777777877617151",
|
||||
m: 13,
|
||||
r: 2
|
||||
});
|
||||
});
|
||||
it('test 7', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: "389274238947216444871600001871964319565192765874149",
|
||||
m: 117,
|
||||
r: 44
|
||||
});
|
||||
});
|
||||
it('test 8', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: "89457115510016156219817846189181057618965150496979174671534084187",
|
||||
m: "1897166415676096761",
|
||||
r: "16840615e646a4c5c8d"
|
||||
});
|
||||
});
|
||||
it('test 9', function () {
|
||||
testExp({
|
||||
g: "4c3399bebab284bc7f9056efe17ea39db324ffa1d52dc1542eb16a749570789359f192535b7bcb514b36be9cdb4fb2a6ba3ad6b4248034e9a1d2a8612cd9d885242b4f679524121b74f79d7db14859fccde1c0dfe6ef002dcbc777ab5fcf4432",
|
||||
e: "000322e6b6dafe138ccd6a991977d19",
|
||||
m: "a5091daa41997943e3c98469e93377f668d05d8059bc53f72aaacdac3729a3070dc7439a5171160bf9ec2826b7191b03b0e84b28e14dd376de35d29a96f686666e053ab62a41ebc2b5f52e8cf06254100fd153a1cda4485f170c39c54689e52d",
|
||||
r: "554336ea044782d29f091117cfeaeee2334b4242bd7428d0bba3ce5325781dc219e891e54698cb0193ffe7c6fc07f1808f6685e64b6a082815f6afd2e16c7a61316b5e3e59cd8b3984d5a76c8e173f8615f7dceac0c99e27e4abfb1278dfa67f"
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
31
test/testutils.js
Normal file
31
test/testutils.js
Normal file
@@ -0,0 +1,31 @@
|
||||
var ripple = require('../src/js/ripple');
|
||||
|
||||
exports.get_config = get_config;
|
||||
|
||||
function get_config() {
|
||||
var config = { };
|
||||
try {
|
||||
config = require('./config');
|
||||
} catch(exception) {
|
||||
config = require('./config-example');
|
||||
}
|
||||
return load_config(config);
|
||||
};
|
||||
|
||||
exports.load_config = load_config;
|
||||
|
||||
function load_config(config) {
|
||||
return load_module('config').load(config);
|
||||
};
|
||||
|
||||
exports.load_module = load_module;
|
||||
|
||||
function load_module(name) {
|
||||
if (process.env.RIPPLE_LIB_COV) {
|
||||
return require('../src-cov/js/ripple/' + name)
|
||||
} else if (!ripple.hasOwnProperty(name)) {
|
||||
return require('../src/js/ripple/' + name);
|
||||
} else {
|
||||
return require('../src/js/ripple')[name];
|
||||
}
|
||||
};
|
||||
92
test/transaction-queue-test.js
Normal file
92
test/transaction-queue-test.js
Normal file
@@ -0,0 +1,92 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var Transaction = utils.load_module('transaction').Transaction;
|
||||
var TransactionQueue = utils.load_module('transactionqueue').TransactionQueue;
|
||||
|
||||
describe('Transaction queue', function() {
|
||||
it('Push transaction', function() {
|
||||
var queue = new TransactionQueue();
|
||||
var tx = new Transaction();
|
||||
|
||||
queue.push(tx);
|
||||
|
||||
assert.strictEqual(queue.length(), 1);
|
||||
});
|
||||
|
||||
it('Remove transaction', function() {
|
||||
var queue = new TransactionQueue();
|
||||
var tx = new Transaction();
|
||||
|
||||
queue.push(tx);
|
||||
queue.remove(tx);
|
||||
|
||||
assert.strictEqual(queue.length(), 0);
|
||||
});
|
||||
|
||||
it('Remove transaction by ID', function() {
|
||||
var queue = new TransactionQueue();
|
||||
var tx = new Transaction();
|
||||
|
||||
queue.push(tx);
|
||||
|
||||
tx.submittedIDs = [
|
||||
'1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B',
|
||||
'2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'
|
||||
];
|
||||
|
||||
queue.remove('3A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B');
|
||||
|
||||
assert.strictEqual(queue.length(), 1);
|
||||
|
||||
queue.remove('2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B');
|
||||
|
||||
assert.strictEqual(queue.length(), 0);
|
||||
});
|
||||
|
||||
it('Add sequence', function() {
|
||||
var queue = new TransactionQueue();
|
||||
queue.addReceivedSequence(1);
|
||||
assert(queue.hasSequence(1));
|
||||
});
|
||||
|
||||
it('Add ID', function() {
|
||||
var queue = new TransactionQueue();
|
||||
var tx = new Transaction();
|
||||
queue.addReceivedId('1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B', tx);
|
||||
assert.strictEqual(queue.getReceived('2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), void(0));
|
||||
assert.strictEqual(queue.getReceived('1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
|
||||
});
|
||||
|
||||
it('Get submission', function() {
|
||||
var queue = new TransactionQueue();
|
||||
var tx = new Transaction();
|
||||
|
||||
queue.push(tx);
|
||||
|
||||
tx.submittedIDs = [
|
||||
'1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B',
|
||||
'2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'
|
||||
];
|
||||
|
||||
assert.strictEqual(queue.getSubmission('1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
|
||||
assert.strictEqual(queue.getSubmission('2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
|
||||
assert.strictEqual(queue.getSubmission('3A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), void(0));
|
||||
});
|
||||
|
||||
it('Iterate over queue', function() {
|
||||
var queue = new TransactionQueue();
|
||||
var count = 10;
|
||||
|
||||
for (var i=0; i<count; i++) {
|
||||
queue.push(new Transaction());
|
||||
}
|
||||
|
||||
queue.forEach(function(tx) {
|
||||
assert(tx instanceof Transaction);
|
||||
--count;
|
||||
});
|
||||
|
||||
assert.strictEqual(count, 0);
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user