Compare commits

...

293 Commits

Author SHA1 Message Date
Geert Weening
b14343f3cf [TASK] shrinkwrap dependencies 2015-01-06 14:48:13 -08:00
Geert Weening
732b50dea7 [TASK] bump version to 0.10.0 2015-01-06 14:48:05 -08:00
Geert Weening
be3bbe9b61 [DOC] update release notes 2015-01-06 14:46:44 -08:00
Geert Weening
1eaad617cb [TASK] bump version to 0.10.0-rc2 2014-12-11 00:20:50 -08:00
wltsmrz
3c21994adc [FIX] Fix empty queue check on reconnect 2014-12-10 23:58:00 -08:00
wltsmrz
d15d14e197 [FIX] Fix undefined Transaction.prototype.summary() 2014-12-10 14:17:28 -08:00
Geert Weening
e32694dc79 [TASK] bump version to 0.10.0-rc1 2014-12-10 14:02:48 -08:00
wltsmrz
6ec8124287 [DOC] Update release notes 2014-12-10 14:00:10 -08:00
Geert Weening
2222adfc10 [DOC] update release notes 2014-12-10 13:29:47 -08:00
Geert Weening
fcc2377657 Merge pull request #223 from geertweening/feature/memo_format_type
[FEATURE] improve memo support
2014-12-10 13:08:04 -08:00
Geert Weening
1704ac4ae1 [FEATURE] improve memo support
- add MemoFormat property for memo
- MemoFormat and MemoType must be valid ASCII
- Memo content is converted on the serialization level
- add parsed_* version of Memo content if the parser understand the format
- support `text` and `json` MemoFormat

[FIX] double serialization overriding Memo contents

The copy made in from_json wasn't a deep copy
2014-12-10 13:07:44 -08:00
Geert Weening
666e4348e0 Merge pull request #222 from boxbag/transaction-set-fee
[FEATURE] allow per transaction fees to be set
2014-12-10 13:07:20 -08:00
Bo Chen
9b22f279bc [FEATURE] allow per transaction fees to be set 2014-12-10 11:52:16 -08:00
Geert Weening
0835de983b Merge pull request #221 from ripple/transaction-updates
Transaction updates
2014-12-10 11:37:36 -08:00
wltsmrz
1a892d58fc Rework tef and tel-class transaction error handling
Do not finalize tef or tel-class errors until LastLedgerSequence is
exceeded. Transactions that fail in this way will now be aborted
with a tej-class error. Errors like tefALREADY and tefMAX_LEDGER
should now be opaque to the user and have no consequence in
determining a final state.
2014-12-10 11:30:45 -08:00
wltsmrz
73a3cce4a4 Do not bump LastLedgerSequence on resubmit 2014-12-10 11:30:45 -08:00
wltsmrz
d5ef4774fa Make LastLedgerSequence offset configurable, default to 3 2014-12-10 11:30:45 -08:00
wltsmrz
c5bd4239a4 Cleanup
- Deprecate 'save' event
- Add TransactionQueue.getMinLedger(), use this as ledger_index_min
  in account_tx request on reconnect
- tx.sign() no longer accepts a callback
- Add various setters and jsdoc to transaction.js
- Normalize setters, e.g. sourceTag() and destinationTag()
- Minor optimization in call to tx.hash() in TransactionManager prior
  to submit; allow `serialized` argument to tx.hash() such that the
  transaction is not serialized twice
2014-12-10 11:30:45 -08:00
Geert Weening
634e811888 Merge pull request #220 from geertweening/update_sjcl
[TASK] update sjcl
2014-12-08 15:21:13 -08:00
Geert Weening
3204998fcb [TASK] update sjcl
- native support for k256
- improved entropy by taking advantage of platform crypto

- remove unnecessary sjcl overrides from sjcl-secp256k1.js
- updated ripple-lib curve instantiations to use k256
- add curve override so c256 points to k256

16dde36fa2
2014-12-08 15:02:24 -08:00
Geert Weening
12e428733a [TASK] bump version to 0.9.4 2014-12-05 15:48:24 -08:00
Geert Weening
9cc6ad09a9 [DOC] update release notes 2014-12-05 15:47:46 -08:00
Geert Weening
84abb5962e [TASK] bump ripple-lib to 0.9.4-rc2 2014-12-04 12:56:17 -08:00
Geert Weening
4bba55d2dc Revert "[FEATURE] improve memo support"
This reverts commit 89adcf4f4e.
2014-12-04 12:54:48 -08:00
Geert Weening
b4cabad44e [TASK] bump version to 0.9.4-rc1 2014-12-04 11:03:01 -08:00
Geert Weening
28cc0f9e3b [DOC] update release notes 2014-12-04 11:02:20 -08:00
wltsmrz
95a2cc18fe Merge pull request #213 from geertweening/feature/memo_format_type
[FEATURE] improve memo support
2014-12-02 00:00:10 -08:00
Geert Weening
8e315a9859 [DOC] update generate wallet example
to take advantage of randomness collected from a rippled
2014-12-01 17:54:34 -08:00
Geert Weening
89adcf4f4e [FEATURE] improve memo support
- add MemoFormat property for memo
- MemoFormat and MemoType must be valid ASCII
- Memo content is converted on the serialization level
- add parsed_* version of Memo content if the parser understand the format
- support `text` and `json` MemoFormat
2014-12-01 09:48:56 -08:00
Geert Weening
3a6c5e41c9 Merge pull request #217 from ripple/orderbook-cleanup
Cleanup, normalize offers from book_offers and transaction stream
2014-11-30 14:14:01 -08:00
wltsmrz
86ed24b94c Cleanup, normalize offers from book_offers and transaction stream 2014-11-29 15:24:15 -08:00
wltsmrz
c792c471c3 Merge pull request #215 from ripple/fix-precision-rounding
Fix to_human precision rounding
2014-11-26 18:31:29 -08:00
wltsmrz
e371cc2c3c Fix to_human precision rounding 2014-11-26 11:32:15 -08:00
Geert Weening
ccf218c8f0 Merge pull request #214 from ripple/fix-fractional-drops
Fix fractional drops in funded taker_pays setter
2014-11-26 09:16:55 -08:00
wltsmrz
0d7fc0a573 Fix fractional drops in funded taker_pays setter 2014-11-25 21:10:57 -08:00
Geert Weening
74cacd5209 [DOC] update offer example 2014-11-19 18:04:45 -08:00
Geert Weening
bb79cf2a87 [TASK] bump version to 0.9.3 2014-11-19 12:09:18 -08:00
Geert Weening
28451df1a8 Merge branch 'develop' into release 2014-11-19 11:32:19 -08:00
Geert Weening
38e288f62a [TASK] bump version to 0.9.2-rc3 2014-11-19 11:31:19 -08:00
Geert Weening
905f908450 [DOC] update release notes 2014-11-19 11:31:19 -08:00
wltsmrz
672171fd0c Merge pull request #211 from jks-liu/fix-link
Fix link in README
2014-11-19 05:24:58 -08:00
Jks Liu
520660ecbc Fix link in README 2014-11-19 16:42:25 +08:00
Geert Weening
06acb5faf2 [TASK] bump version to 0.9.2-rc3 2014-11-18 11:58:41 -08:00
Geert Weening
d43fa03f05 [DOC] update release notes 2014-11-18 11:57:49 -08:00
Geert Weening
baed1aaf92 Merge branch 'release' into develop 2014-11-18 11:54:17 -08:00
wltsmrz
cc229e803c Merge pull request #210 from geertweening/fix/max_fee
[TASK] change default `max_fee` for Remote to 1 XRP
2014-11-18 11:48:37 -08:00
Geert Weening
d6b1728c23 [TASK] change default max_fee for Remote to 1 XRP 2014-11-18 10:47:28 -08:00
wltsmrz
bc5dcc359c Merge pull request #209 from ximinez/ledger_accept
Request ledger_accept returns the Request, not Remote
2014-11-17 12:08:38 -08:00
Edward Hennis
ced07e1d6b Request ledger_accept returns the Request, not Remote 2014-11-17 15:02:45 -05:00
Geert Weening
cffffd9591 [TASK] bump version to 0.9.3-rc2 2014-11-14 10:24:44 -08:00
Geert Weening
b8766e263f [DOC] update release notes 2014-11-14 10:24:00 -08:00
Geert Weening
fc426d5764 Merge branch 'release' into develop 2014-11-14 10:16:34 -08:00
Geert Weening
056d2381cd Merge pull request #208 from ripple/relocate-presubmit
Relocate presubmit emission to immediately before transaction submit
2014-11-14 10:14:02 -08:00
Geert Weening
2932a0ec5f [DOC] add doc that accountRequest throws
if a marker is provided, but no ledger_index or ledger_hash
2014-11-14 10:11:26 -08:00
Geert Weening
d3d85a3fcf [DOC] add doc that accountRequest throws
if a marker is provided, but no ledger_index or ledger_hash
2014-11-14 10:11:13 -08:00
wltsmrz
7a1feaa897 Relocate presubmit emission to immediately before transaction submit 2014-11-13 21:44:20 -08:00
Geert Weening
5f3cf72cc6 Merge pull request #207 from shekenahglory/develop
[TASK] binformat: update fields to match rippled
2014-11-13 14:13:52 -08:00
Matthew Fettig
cae980788e binformat: update fields to match rippled 2014-11-13 11:14:46 -08:00
Geert Weening
df763b8765 Merge pull request #205 from ripple/core-build
Add core build
2014-11-12 10:51:45 -08:00
wltsmrz
365085809e Add note on restricted browser builds 2014-11-12 03:04:51 -08:00
Geert Weening
3ee7998261 [TASK] bump version to 0.9.3-rc1 2014-11-11 17:52:30 -08:00
Geert Weening
6fb9ed8312 [DOC] update release notes 2014-11-11 17:52:13 -08:00
Geert Weening
89f79c35f5 Merge pull request #206 from geertweening/fix/tec_wait_for_validated
[TASK] wait for validation before returning tec error
2014-11-11 17:49:38 -08:00
Geert Weening
6bdd4b2670 [TASK] wait for validation before returning tec error 2014-11-11 16:54:25 -08:00
Geert Weening
acd79d19e2 [TASK] bump version to 0.9.2 2014-11-11 12:14:04 -08:00
Geert Weening
674d4a957d [TASK] bump version to 0.9.2-rc6 2014-11-11 11:51:54 -08:00
Geert Weening
bdbf264771 [FIX] support string '0' being interpreted as XRP 2014-11-11 11:51:12 -08:00
wltsmrz
8f17873da2 Remove server._computeFee(Transaction), require fee units argument 2014-11-11 06:24:34 -08:00
wltsmrz
b0cac776ee Throw an error when trying to use unavailable class in WebPack build 2014-11-11 06:23:37 -08:00
wltsmrz
625dba4d85 Add build-core gulp task 2014-11-11 05:30:50 -08:00
Geert Weening
261b72d0fc [DOC] update API reference 2014-11-10 14:32:35 -08:00
Geert Weening
b5b167ef6d [DOC] update README and GUIDES
to match current API's
2014-11-10 10:14:11 -08:00
Geert Weening
66d21b24cd [TASK] bump ripple-lib to 0.9.2-rc5 2014-11-07 18:22:23 -08:00
Geert Weening
5a084ea3cc [TEST] fix broken tests
as a result of updating account_info request in account.js
2014-11-07 18:22:05 -08:00
Geert Weening
486944fa4c [FIX] request account info in account.js 2014-11-07 18:04:04 -08:00
Geert Weening
b63a76d298 [TASK] bump version to 0.9.2-rc4 2014-11-07 17:45:17 -08:00
Geert Weening
31045039c0 [DOC] update release notes 2014-11-07 17:44:30 -08:00
Geert Weening
6f5d1104aa [TASK] change accountRequest method signature
`(type, options, callback)` where the last argument will be considered for callback if it's a function
2014-11-07 17:08:01 -08:00
Geert Weening
3c9660203b Merge pull request #203 from geertweening/feature/marker_valid_ledger
[FEATURE] check for valid ledger when using marker
2014-11-07 12:57:57 -08:00
Geert Weening
29e1423f84 [FEATURE] check for valid ledger when using marker
when using a marker on an account request, a valid ledger_index or ledger_hash is required, otherwise the results can't be guaranteed to be reliable

consolidated test values for addresses, indexes and hashes
2014-11-07 11:18:24 -08:00
Geert Weening
e42e67e259 [TASK] consolidate accountRequest and pagingAccountRequest
update `requestAccountCurrencies` and  `requestAccountInfo`
2014-11-07 09:52:17 -08:00
Geert Weening
ed018282c4 [TASK] remove clone in favor of copy and update
see 'object' case in `parse_json`
2014-11-06 22:58:04 -08:00
Geert Weening
fbe015758c [TASK] bump version to 0.9.2-rc3 2014-11-06 21:48:01 -08:00
Geert Weening
7e24a81764 [DOC] update release notes 2014-11-06 21:47:44 -08:00
Geert Weening
9ab77e90fe Merge pull request #202 from geertweening/feature/paging_account_request
[FEATURE] add paging account request
2014-11-06 21:41:44 -08:00
Geert Weening
ae3ed699db Merge pull request #201 from geertweening/fix/from_human_full_name_native
[FIX] from_human 'XRP' with full name
2014-11-06 21:41:30 -08:00
Geert Weening
0c22a9753e Merge pull request #199 from geertweening/fix/amount_cap
[FIX] cap IOU Amounts to their max and min value
2014-11-06 21:41:08 -08:00
Geert Weening
a447f6b723 [TEST] add missing testcases for interest bearing currencies
- `get_interest_at` by providing a Date object
- `get_interest_at` for a Currency without interest
2014-11-06 21:38:00 -08:00
Geert Weening
a8ef614b81 [FIX] Currency constructor with Currency object
clone the given Currency instance in `parse_json`
2014-11-06 21:36:57 -08:00
Geert Weening
9025e8bfa8 [FIX] get interest for currency without interest
- `get_interest_at` for a currency without interest would not hit the intended check since the function was used as variable instead of calling the method
2014-11-06 21:35:41 -08:00
Geert Weening
722f4e175d [FEATURE] add paging account request
some requests return results that can be paged through, e.g. `account_lines`
use `limit` and `marker` options to specify results per response and position

change `requestAccountLines` and `requestAccountOffers` to use the `pagingAccountRequest`
2014-11-06 18:40:45 -08:00
Geert Weening
1ad6e5a15f [FIX] from_human 'XRP' with full name
from_human 'XRP - Ripples' should result in native XRP
2014-11-05 16:54:45 -08:00
Geert Weening
3554572db7 [TASK] bump version to 0.9.2-rc2 2014-11-05 16:32:37 -08:00
Geert Weening
f1abff962f Merge pull request #200 from boxbag/fix-max-fee
[FIX] fix test and do not set tx_json.Fee in maxFee method
2014-11-05 16:07:01 -08:00
Geert Weening
f05941fbc4 [FIX] cap IOU Amounts to their max and min value
respect rippled's limits
2014-11-05 16:04:02 -08:00
Bo Chen
237c46d5a0 [FIX] fix test and do not set tx_json.Fee in maxFee method 2014-11-05 15:59:57 -08:00
Geert Weening
76cfb69d9f [TASK] bump version to 0.9.2-rc1 2014-11-05 14:38:47 -08:00
Geert Weening
7610df0fbb [DOC] update release notes 2014-11-05 14:38:35 -08:00
Geert Weening
8bc935aa62 Merge pull request #198 from boxbag/transaction-max-fee
[FEATURE] set max fee the submitter of a transaction is willing to pay
2014-11-05 09:27:26 -08:00
Bo Chen
24587fab9c [FEATURE] set max fee the submitter of a transaction is willing to pay 2014-11-04 14:13:42 -08:00
Geert Weening
0248475473 [DOC] update description in package.json 2014-10-31 18:52:09 -07:00
Geert Weening
d2fa5c4b12 [TASK] bump version to 0.9.1 2014-10-30 10:01:16 -07:00
Geert Weening
c60c0cb6e0 [TASK] bump version to 0.9.1-rc3 2014-10-28 17:35:29 -07:00
Geert Weening
cdf1112666 [FIX] ledgerSelect setting both ledger_index and ledger_hash 2014-10-28 17:33:55 -07:00
Geert Weening
d861bb2e34 [TASK] bump version to 0.9.1-rc2 2014-10-27 17:32:37 -07:00
Geert Weening
006849a3d5 [DOC] update release notes 2014-10-27 17:32:18 -07:00
wltsmrz
a3c1d06eba Change initial account transaction sequence to 1 2014-10-27 17:29:51 -07:00
Geert Weening
4bd1e7a2bc [TASK] bump version to 0.9.1-rc1 2014-10-27 16:40:46 -07:00
Geert Weening
68643f3118 [DOC] update release notes 2014-10-27 16:37:14 -07:00
Geert Weening
560dfc8ae6 Merge branch 'release' into develop 2014-10-27 16:27:52 -07:00
Geert Weening
b0459e096b [TASK] bump version to 0.9.0 2014-10-27 16:25:34 -07:00
Geert Weening
2a0dfc4587 [DOC] update release notes 2014-10-27 16:25:09 -07:00
Geert Weening
2dcd5f94fb [TASK] shrinkwrap packages for dependency locking 2014-10-27 16:24:18 -07:00
Geert Weening
13685d03e1 Merge pull request #195 from ripple/use-ledgerselect
Switch account requests to use ledgerSelect rather than ledgerChoose
2014-10-27 16:07:40 -07:00
wltsmrz
278df9025a Switch account requests to use ledgerSelect rather than ledgerChoose 2014-10-27 15:35:07 -07:00
Geert Weening
cb608406f8 Merge pull request #191 from geertweening/fix/undefined_remote
[FIX] transaction without explicit remote
2014-10-27 12:09:59 -07:00
Geert Weening
f4a55d03d3 Merge pull request #192 from geertweening/fix/amount_precision
[FIX] confusion between precision and min_precision
2014-10-26 19:21:09 -04:00
Geert Weening
d3b6b8127c [FIX] transaction without explicit remote
remote was instantiated as an object and checks through the class for `this.remote` would pass and cause 
unintended behavior

e.g. `.complete()` would view an undefined remote as untrusted and not allow local signing
e.g. calling `_computeFee()` with an undefined remote would crash ripple-lib
2014-10-26 19:09:04 -04:00
wltsmrz
bc1f9f8a28 Fix account root request ledger argument #121 2014-10-26 15:39:17 -07:00
wltsmrz
9a5c9aea75 Merge pull request #193 from professorhantzen/patch-1
[FIX] correct usage example with surrounding apostrophes
2014-10-24 18:12:32 -07:00
professorhantzen
f1004c6db2 Correct usage example with surrounding apostrophes 2014-10-25 13:14:07 +13:00
Geert Weening
7708c64576 [FIX] confusion between precision and min_precision
`precision` expresses precision within the provided value and won't expand it if the max expression of the value is smaller
`min_precision` will expand the value if the max expression of the value is smaller
2014-10-23 17:59:53 -07:00
Geert Weening
0527b8c981 [TASK] move gulp-bump to devDependencies 2014-10-22 12:07:36 -07:00
Geert Weening
13f89e2fcc [TASK] bump version to 0.9.0-rc5 2014-10-21 15:05:02 -07:00
Geert Weening
69a0a473a6 [DOC] update release notes 2014-10-21 15:04:52 -07:00
Geert Weening
4ab82d7e01 Merge pull request #188 from geertweening/fix/amount_precision
[FIX] amount precision bugs
2014-10-21 15:01:50 -07:00
Geert Weening
4be209e286 [FIX] amount precision bugs
- leading 0's for fraction part disappearing
- first decimal lower than 5 wouldn't result in dropping the decimal
2014-10-21 13:22:42 -07:00
Geert Weening
8b10325895 [TASK] bump version to 0.9.0-rc4 2014-10-16 14:11:36 -07:00
Geert Weening
70bf600247 [DOC] update release notes 2014-10-16 14:11:14 -07:00
Geert Weening
d42e06d48b [FIX] fraction_part is a string 2014-10-16 14:05:26 -07:00
wltsmrz
9c080b6790 Merge pull request #187 from ripple/amount_precision
[FIX] Amount.to_human() precision slicing instead of rounding
2014-10-16 14:00:29 -07:00
Geert Weening
033257b03b [FIX] Amount.to_human() precision slicing instead of rounding 2014-10-16 13:54:42 -07:00
Geert Weening
39d8bcdfc2 [TASK] bump version to 0.9.0-rc3 2014-10-15 11:32:16 -07:00
Geert Weening
2ddcb4e2b7 [DOC] update release notes 2014-10-15 11:31:52 -07:00
Geert Weening
d972718a53 Merge pull request #185 from shekenahglory/fix/serialize
[FIX] binformat: added missing TransactionResult options
2014-10-14 11:37:35 -07:00
Matthew Fettig
6abed8dd53 [FIX] binformat: added missing TransactionResult options 2014-10-14 11:32:33 -07:00
Geert Weening
e74e697b45 [TASK] bump version to 0.9.0-rc2 2014-10-09 14:46:12 -07:00
Geert Weening
26c59e8565 [DOC] update release notes 2014-10-09 14:45:44 -07:00
Geert Weening
a5e83c4f23 [FIX] run test by _mocha to capture coverage 2014-10-09 14:35:14 -07:00
wltsmrz
900c4bbd2e Merge pull request #184 from geertweening/show_interest_flag
[FEATURE] add flag to show or hide interest in to_human/to_json
2014-10-09 14:31:43 -07:00
Geert Weening
947ec3edc2 [FEATURE] add flag to show or hide interest in to_human/to_json
The show_interest flag will default to true for interest bearing currencies and false for currencies without interest
2014-10-09 14:31:02 -07:00
Geert Weening
957f10d9f1 [TASK] bump version to 0.9.0-rc1 2014-10-09 09:40:31 -07:00
Geert Weening
89aa54dff8 [DOC] update release notes 2014-10-09 09:39:15 -07:00
Geert Weening
bb76530e4b Merge pull request #179 from geertweening/develop
[FEATURE] make maxLoops in seed.get_key() optional
2014-10-09 09:32:17 -07:00
wltsmrz
011e2cc1e3 Merge pull request #182 from shekenahglory/fix/attestation
[FIX] vault client: URI decode attestation token
2014-10-08 18:41:38 -07:00
Matthew Fettig
4c594f8964 [FIX] vault client: URI decode attestation token 2014-10-08 18:17:30 -07:00
Geert Weening
1fcfcf2392 [TASK] bump version to 0.8.3-rc1 2014-10-06 18:23:59 -07:00
Geert Weening
6311abff81 [DOC] update release notes 2014-10-06 18:23:05 -07:00
Geert Weening
ed2da57475 Merge pull request #181 from shekenahglory/feature/identity
Feature/identity
2014-10-06 18:15:38 -07:00
Matthew Fettig
778ccd4805 [TASK] vault client: tests for attestation routes and full summary 2014-10-06 18:03:52 -07:00
Matthew Fettig
327c35252f [FEATURE] vault client: update attestation and attestation summary 2014-10-03 17:17:09 -07:00
Matthew Fettig
5e7af2fba4 [TASK] switch to new attestation endpoint 2014-10-03 17:17:09 -07:00
Matthew Fettig
dce15bc579 [TASK] vault client: add parameters to attest 2014-10-03 17:17:08 -07:00
Matthew Fettig
d5e32db954 [CHORE] vault client: add type to attest parameters 2014-10-03 17:17:08 -07:00
Matthew Fettig
bdfa83592b [FEATURE] identity functions 2014-10-03 17:17:08 -07:00
Geert Weening
23e473b688 [FEATURE] make maxLoops in seed.get_key optional
default to 1 or the index of the requested account +1
2014-10-02 17:26:01 -07:00
wltsmrz
0dfd3a0ae0 Merge pull request #178 from ripplerm/develop
configurable maxAttempts
2014-10-01 02:07:04 -07:00
ripplerm
d107092540 configurable maxAttempts 2014-10-01 14:43:44 +08:00
wltsmrz
c2f379d3b3 Merge pull request #176 from shekenahglory/develop
[FIX] change handling of requestLedger options
2014-09-28 00:34:30 -07:00
Matthew Fettig
57b70300f5 [FIX] change handling of requestLedger options
ledger_index and ledger_hash were being set to true
instead of the specified value, and according to API
docs 'closed','validated', and 'current' can be
supplied to the ledger_index field to get the most
recent of the specified type
2014-09-26 21:41:22 -07:00
Geert Weening
eeba86f9c5 [TASK] bump version to 0.8.2 2014-09-25 10:05:57 -07:00
Geert Weening
e0d68e60ec Merge pull request #172 from booxood/modify-docs-guides-md
Update docs/GUIDES.md
2014-09-24 23:44:08 -07:00
Liucw
254248486b Update docs/GUIDES.md 2014-09-25 14:34:29 +08:00
Geert Weening
1b57cc6d35 [TASK] bump verison to 0.8.2-rc2 2014-09-22 23:59:12 -07:00
Geert Weening
77234f256d [DOC] update release notes 2014-09-22 23:58:49 -07:00
Geert Weening
795d31d2db Merge branch 'release' into develop 2014-09-22 23:50:45 -07:00
wltsmrz
f3f10fd9bd Fix requestLedger arguments 2014-09-18 02:03:29 -07:00
wltsmrz
7100b4be8d Emit server with subscription events 2014-09-18 01:41:23 -07:00
wltsmrz
b1a7200d1b Deprecate account_tx map/reduce/filter 2014-09-18 01:28:43 -07:00
Geert Weening
5d8bb541c6 [TASK] bump version to 0.8.2-rc1 2014-09-17 15:02:44 -07:00
Geert Weening
b51c59b23a [TASK] update release notes 2014-09-17 15:02:21 -07:00
wltsmrz
2cd434e861 Fix orderbook reset on reconnect 2014-09-17 13:42:24 -07:00
wltsmrz
1599eb9629 Add potential missing error handlers 2014-09-17 13:40:49 -07:00
wltsmrz
8ef7481858 Allow mixed letters and numbers in currencies 2014-09-17 13:39:49 -07:00
Geert Weening
344d478b3f [TASK] bump version to 0.8.1 2014-09-15 16:52:23 -07:00
Geert Weening
39b7e27aa6 [TASK] bump version to 0.8.1-rc3 2014-09-12 14:26:25 -07:00
Geert Weening
b1876b4f77 [DOC] update release notes 2014-09-12 14:26:02 -07:00
wltsmrz
db3b41d1ba Merge pull request #167 from ximinez/ripd-549
[BUG] Fix Amount.to_human_full()
2014-09-12 12:16:03 -07:00
Edward Hennis
02b5d14d0f [BUG] Fix Amount.to_human_full()
* Amount.is_native is a function, but was referenced as a bool.
* Duplicate to_text_full tests to test to_human_full.
2014-09-12 10:52:46 -04:00
wltsmrz
0120044c96 Fix undefined fee states when connecting to a rippled that is syncing 2014-09-11 23:44:21 -07:00
wltsmrz
ad6304e857 Merge pull request #166 from geertweening/develop
[DOC] update README install instructions
2014-09-10 18:54:45 -07:00
Geert Weening
7cba84b8cf [DOC] update README install instructions 2014-09-10 18:51:59 -07:00
Geert Weening
5a9a4be163 [TASK] bump version to 0.8.1-rc2 2014-09-10 17:54:11 -07:00
Geert Weening
4d1a31d3c9 Revert "[FOLD] Add postinstall step to build the sjcl.js"
This reverts commit 42c853dbf4.
2014-09-10 17:53:29 -07:00
Geert Weening
6e3ceec4e5 [TASK] update publish scripts 2014-09-10 17:33:52 -07:00
Geert Weening
bc7d3c0af8 [TASK] add release candidate publish script 2014-09-10 17:32:03 -07:00
Geert Weening
519ddee092 [TASK] bump version to 0.8.1-rc1 2014-09-10 17:28:34 -07:00
Geert Weening
3e0fcc5b8b [DOC] update release notes 2014-09-10 17:28:00 -07:00
wltsmrz
b1972985c4 Merge pull request #164 from geertweening/develop
[FIX] return null for _getServer if there's no connected server
2014-09-08 12:04:22 -07:00
Geert Weening
51c42e9257 [FIX] return null for _getServer if there's no connected server 2014-09-08 11:54:48 -07:00
Geert Weening
86dcbcc671 [TASK] add exit_on_error calls to publish script 2014-09-08 11:54:47 -07:00
wltsmrz
3b7cd9d84f Merge pull request #163 from stevenzeiler/wallet
[TASK] Update package.json for ripple-wallet-generator patch.
2014-09-06 02:18:08 -07:00
Steven Zeiler
1073ec6214 [TASK] Update package.json for ripple-wallet-generator patch.
Removed node_modules from git repo of ripple-wallet-generator
2014-09-05 21:19:09 -07:00
wltsmrz
14a5e42a63 Merge pull request #162 from sublimator/develop
Fix #160, Seed.parse_json of hex
2014-09-05 20:56:53 -07:00
Nicholas Dudfield
b4564a86b4 Fix #160, Seed.parse_json of hex 2014-09-06 10:45:26 +07:00
wltsmrz
03386a61e9 Merge pull request #161 from stevenzeiler/wallet
[FEATURE] Add Wallet class that generates wallets.
2014-09-05 18:22:55 -07:00
Steven Zeiler
8bb2623360 [FEATURE] Add Wallet class that generates wallets.
[DOC] Comment out logged wallet.

[FIX] Use var instead of const.
2014-09-05 18:20:46 -07:00
wltsmrz
ab0e4188b3 Merge pull request #159 from ximinez/ripd-549
Clean up
2014-09-05 13:03:48 -07:00
Edward Hennis
42c853dbf4 [FOLD] Add postinstall step to build the sjcl.js 2014-09-05 14:50:29 -04:00
Geert Weening
ce48a1793b Merge branch 'release' into develop 2014-09-05 10:24:05 -07:00
Geert Weening
6177543d98 [TASK] add publish script 2014-09-05 10:23:59 -07:00
Geert Weening
9697bfa817 [TASK] add publish to bower script 2014-09-05 10:23:59 -07:00
Geert Weening
70425ab5c8 [TASK] bump version to 0.8.0 2014-09-05 10:19:58 -07:00
wltsmrz
7cccb451d2 Show field name in serialization failure 2014-09-04 20:13:04 -07:00
wltsmrz
a39fb9d551 Minor cleanup 2014-09-04 19:53:16 -07:00
wltsmrz
8f7cdc6e4f Prevent setting LastLedgerSequence of NaN. Fixes a potential ambiguous serialization error 2014-09-04 19:50:56 -07:00
wltsmrz
8f7e365b03 Wait until remote is connected before emitting ledger_closed events 2014-09-04 18:51:00 -07:00
Edward Hennis
64735e523f Clean up
* Make npm test runnable in Windows.
* Fix paths in README.md
* Ignore all build output files
2014-09-04 20:05:04 -04:00
Geert Weening
f126610219 [TASK] add publish to bower script 2014-09-04 16:11:59 -07:00
Geert Weening
2caef539ce [TASK] bump version to 0.8.0-rc3 2014-09-04 15:43:01 -07:00
Geert Weening
468fb87749 [DOC] update release notes 2014-09-04 15:42:36 -07:00
Geert Weening
4f4808ff15 Merge pull request #158 from justmoon/amount_constants
Amount: Constants should be static fields on the class, not a separate export.
2014-09-04 15:30:12 -07:00
Stefan Thomas
e6bbca7df1 [TASK] Amount: Constants should be static fields on the class, not a separate export. 2014-09-04 15:12:14 -07:00
Geert Weening
e7d1095be2 Merge pull request #157 from geertweening/release
Prepare 0.8.0-rc2 release
2014-09-04 10:16:14 -07:00
Geert Weening
a08d5ce6e5 [TASK] bump version to 0.8.0-rc2 2014-09-04 10:15:47 -07:00
Geert Weening
fec2f5578d [DOC] update release notes 2014-09-04 10:14:45 -07:00
Geert Weening
4869e30914 Merge branch 'release' into develop 2014-09-04 10:10:42 -07:00
Geert Weening
e1f31765e7 Merge pull request #156 from justmoon/max_value
Add a constant for the maximum number representable as a Ripple value
2014-09-04 09:45:38 -07:00
Stefan Thomas
a3668defa8 [TASK] Add a constant for the maximum number representable as a Ripple value. 2014-09-04 09:27:36 -07:00
wltsmrz
765ff9fa32 Merge pull request #155 from ripple/develop
Update documentation, fix owner_funds not updated in orderbook
2014-08-27 13:49:27 -07:00
wltsmrz
dd04177f83 Update owner_funds 2014-08-27 13:05:54 -07:00
wltsmrz
2e2ab6bffc Update docs 2014-08-26 13:41:27 -07:00
wltsmrz
934cacfc1b Merge pull request #154 from justmoon/new_seed_test
Improve Seed#get_key test cases.
2014-08-25 15:49:58 -07:00
Stefan Thomas
9800fd8f11 [TEST] Previous test cases were using the functionality being tested.
The previous version of these tests was using `get_address` just like the code being tested. It's better to check if the code in question has actually been able to correctly find the public key (which means it also found the correct private key.)
2014-08-25 15:25:07 -07:00
wltsmrz
3e84996788 Merge pull request #153 from ripple/develop
Unfunded orderbook update, add option for disallowing partial server history
2014-08-25 13:27:30 -07:00
wltsmrz
5a3f55d774 Merge pull request #152 from shekenahglory/develop
[CHORE] vault client: get 2fa HMAC instead of ECDSA
2014-08-25 13:11:18 -07:00
Matthew Fettig
dbddc314a6 [CHORE] vault client: get 2fa HMAC instead of ECDSA 2014-08-25 10:45:43 -07:00
wltsmrz
c98f875811 Merge pull request #151 from ripple/incomplete-server-history
Add checks for partial server history
2014-08-22 14:06:29 -07:00
wltsmrz
29a1ffb3b8 Merge pull request #150 from ripple/unfunded-orders
Fix set funded amount with unfunded native currency
2014-08-22 13:25:29 -07:00
wltsmrz
17770ad4c9 Fix set funded amount with unfunded native currency 2014-08-22 13:23:05 -07:00
wltsmrz
cc9ed435eb Add checks for partial server history 2014-08-21 17:21:17 -07:00
wltsmrz
27a723b453 Update readme 2014-08-21 16:05:00 -07:00
wltsmrz
af6c9b6bd2 Merge pull request #147 from ripple/develop
Update unfunded order tracking - fix native/non-native currency discrepancies
2014-08-21 15:08:08 -07:00
wltsmrz
2d3bbecb05 Merge pull request #148 from ripple/unfunded-orders
Fix setting funded amount
2014-08-21 14:32:52 -07:00
wltsmrz
51e4cb15b4 Fix setting funded amount 2014-08-21 14:18:55 -07:00
wltsmrz
5ce91a027c Merge pull request #146 from ripple/unfunded-orders
Update unfunded order tracking - fix native/non-native currency discrepancies
2014-08-20 16:00:08 -07:00
wltsmrz
3cb337e7ec Uncomment tests 2014-08-20 15:53:03 -07:00
wltsmrz
c29f92f05b Fix native/non-native currency discrepancies 2014-08-20 15:50:20 -07:00
wltsmrz
01903cc6d2 Update unfunded order tracking 2014-08-20 14:33:00 -07:00
Geert Weening
fff7a6bc9e Merge pull request #127 from orzFly/patch-1
[FIX] Cannot use demmurage currencies in path_find
2014-08-20 19:29:58 +02:00
Yeechan Lu
678c67622d [FIX] Cannot use demmurage currencies in path_find
Use hex format instead of json for currencies.
2014-08-21 00:32:56 +08:00
wltsmrz
2a6aec94fb Merge pull request #145 from ripple/develop
Update unfunded order tracking
2014-08-19 15:19:10 -07:00
wltsmrz
bc52f33e9c Merge pull request #144 from ripple/unfunded-orders
Convert to IOU format before comparing amounts during updateOfferFunds
2014-08-19 15:18:31 -07:00
wltsmrz
006beeb5f9 Convert to IOU format before comparing amounts during updateOfferFunds 2014-08-19 15:17:21 -07:00
wltsmrz
ff85b3c4c9 Merge pull request #143 from ripple/develop
Update unfunded order tracking
2014-08-19 14:40:41 -07:00
wltsmrz
6c7b2b17dc Merge pull request #142 from ripple/unfunded-orders
Unfunded orders
2014-08-19 14:36:00 -07:00
wltsmrz
131de6661c Update orderbook test 2014-08-19 14:28:26 -07:00
wltsmrz
d416f31801 Fix total account funds being emitted on offer_funds_changed rather than the amount set as taker_gets_funded 2014-08-19 14:28:11 -07:00
wltsmrz
8885a9e3e5 Merge pull request #141 from ripple/develop
Update unfunded order tracking
2014-08-18 18:16:53 -07:00
wltsmrz
27e100f4ee Merge pull request #140 from ripple/unfunded-orders
Unfunded orders
2014-08-18 18:11:51 -07:00
wltsmrz
40dc49bd63 Check listeners length before removeAllListeners, fixing possible WebPack EventEmitter bug 2014-08-18 17:21:08 -07:00
wltsmrz
989509dc07 Revert unfunded offer filtering 2014-08-18 15:34:26 -07:00
wltsmrz
9c3f5fbcd2 Ignore offers with TakerGets of 0 2014-08-18 15:15:16 -07:00
wltsmrz
0917f66cb2 Update orderbook test 2014-08-18 12:37:32 -07:00
wltsmrz
66c56df7dc Fix requestOffers not returning an EventEmitter 2014-08-18 12:22:41 -07:00
wltsmrz
b5fdfa2604 Merge pull request #139 from ripple/develop
Update unfunded order tracking
2014-08-18 11:40:32 -07:00
wltsmrz
a0d4a3c84d Merge pull request #138 from ripple/unfunded-orders
Adjust taker_pays_funded
2014-08-18 11:20:13 -07:00
wltsmrz
d8374b2f49 Improve test coverage 2014-08-16 02:46:55 -07:00
wltsmrz
a2a2162f48 Change how TransferRate is requested 2014-08-16 02:46:32 -07:00
wltsmrz
d845d094db Merge branch 'develop' into unfunded-orders 2014-08-16 02:45:35 -07:00
wltsmrz
81e805fcb9 Fix typo in invalid MemoData error 2014-08-15 20:18:47 -07:00
wltsmrz
81283eeb84 Adjust taker_pays_funded 2014-08-15 20:05:57 -07:00
wltsmrz
60069d0a28 Merge pull request #137 from shekenahglory/develop
[FIX] crypt: add entropy to random words request
2014-08-15 15:51:31 -07:00
Matthew Fettig
7c0561d17f [FIX] crypt: add entropy to random words request 2014-08-15 14:52:11 -07:00
wltsmrz
45ac10b215 Merge pull request #136 from ripple/memos
Add Memo setter
2014-08-14 15:54:46 -07:00
wltsmrz
47f5943cf7 Reverse order of arguments in JSDoc 2014-08-14 13:30:00 -07:00
wltsmrz
73d30242c9 Add Memo setter 2014-08-14 13:26:52 -07:00
wltsmrz
5a4e33a02d Merge pull request #130 from shekenahglory/develop
[FEATURE] blob: force send phone verification token via SMS
2014-08-14 12:04:32 -07:00
Matthew Fettig
edbbbec8f3 [FEATURE] blob: force send phone verification token via SMS 2014-08-13 17:13:54 -07:00
Geert Weening
f8811f27a0 [TASK] bump version to 0.8.0-beta 2014-08-14 00:06:11 +02:00
wltsmrz
ad6138a14e Merge pull request #135 from ripple/develop
Prepare release 0.8.0
2014-08-13 14:51:59 -07:00
wltsmrz
e6fec67ce9 Merge pull request #134 from geertweening/develop
[TASK] add gulp task to bump version and set beta
2014-08-13 14:27:16 -07:00
Geert Weening
08a09fab9c [TASK] throw a new Error 2014-08-13 23:22:49 +02:00
wltsmrz
1bf06bc656 Update HISTORY.md 2014-08-13 14:21:47 -07:00
Geert Weening
cf46808557 [TASK] add gulp task to bump version and set beta 2014-08-13 23:14:50 +02:00
wltsmrz
df5a8656f6 Add delay in server test 2014-08-13 13:54:30 -07:00
Geert Weening
b41f00515b Merge pull request #133 from ripple/develop
Track unfunded orders in the orderbook
2014-08-13 21:50:27 +02:00
wltsmrz
2b22b49f83 Track unfunded orders in the orderbook. #132 2014-08-13 12:28:41 -07:00
wltsmrz
4c0eda95c6 Workaround arguments mutation #126 2014-08-13 12:16:27 -07:00
wltsmrz
92e4644d60 Merge pull request #128 from ripple/passive-hostid
Passively acquire hostid (actually pubkey_node) if present in response t...
2014-08-12 15:14:55 -07:00
wltsmrz
11b67b15e4 Merge pull request #131 from ripple/tefalready-multiserver
Catch all tefALREADY submissions
2014-08-12 15:13:23 -07:00
wltsmrz
565cd59f13 Catch all tefALREADY submissions 2014-08-12 12:29:33 -07:00
shekenahglory
f4643c7b52 Merge pull request #129 from ripple/tefalready-multiserver
Tefalready multiserver
2014-08-12 11:23:53 -07:00
wltsmrz
a292c2841c Fix getServer returning undefined variable rather than null if no servers are specified 2014-08-12 11:13:21 -07:00
wltsmrz
edd57a89c1 Update server test 2014-08-11 19:36:55 -07:00
wltsmrz
0d3bc96672 Update lowest server score for comparison 2014-08-11 19:29:57 -07:00
wltsmrz
cc96d21da6 Passively acquire hostid (actually pubkey_node) if present in response to initial subscribe 2014-08-11 19:27:36 -07:00
wltsmrz
ed4e07907f Wait for uniform tefALREADY response before emitting error - temporary solution 2014-08-11 19:25:53 -07:00
wltsmrz
da6e4be815 Merge pull request #125 from geertweening/develop
[TASK] add bower distribution support, Amount interest tests and travis config
2014-08-05 13:30:04 -07:00
Geert Weening
391cba18b6 [TASK] add travis webhook for gitter 2014-08-05 12:10:14 -07:00
Geert Weening
49f55cea48 [TASK] add gulp task to bump bower version 2014-08-01 12:56:46 -07:00
Geert Weening
fc361841b0 [TASK] add bower support 2014-07-31 17:29:38 -07:00
Geert Weening
5db493df1c [TEST] add test cases for calculating interest 2014-07-31 17:28:53 -07:00
88 changed files with 9106 additions and 7024 deletions

7
.gitignore vendored
View File

@@ -17,7 +17,7 @@
# Ignore object files. # Ignore object files.
*.o *.o
build/ripple*.js build/*.js
tags tags
bin/rippled bin/rippled
Debug/*.* Debug/*.*
@@ -48,4 +48,7 @@ test/config.js
.idea .idea
# Ignore npm-debug # Ignore npm-debug
npm-debug.log npm-debug.log
# Ignore dist folder, build for bower
dist/

View File

@@ -4,3 +4,10 @@ node_js:
script: npm test --coverage script: npm test --coverage
after_success: after_success:
- npm run coveralls - 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

View File

@@ -5,6 +5,8 @@ var rename = require('gulp-rename');
var webpack = require('webpack'); var webpack = require('webpack');
var jshint = require('gulp-jshint'); var jshint = require('gulp-jshint');
var map = require('map-stream'); var map = require('map-stream');
var bump = require('gulp-bump');
var argv = require('yargs').argv;
//var header = require('gulp-header'); //var header = require('gulp-header');
var pkg = require('./package.json'); var pkg = require('./package.json');
@@ -87,6 +89,66 @@ gulp.task('build-debug', [ 'concat-sjcl' ], function(callback) {
}, callback); }, callback);
}); });
/**
* Generate a WebPack external for a given unavailable module which replaces
* that module's constructor with an error-thrower
*/
function buildUseError(cons) {
return 'var {<CONS>:function(){throw new Error("Class is unavailable in this build: <CONS>")}}'
.replace(new RegExp('<CONS>', 'g'), cons);
};
gulp.task('build-core', [ 'concat-sjcl' ], function(callback) {
webpack({
entry: [
'./src/js/ripple/remote.js'
],
externals: [
{
'./transaction': buildUseError('Transaction'),
'./orderbook': buildUseError('OrderBook'),
'./account': buildUseError('Account'),
'./serializedobject': buildUseError('SerializedObject')
}
],
output: {
library: 'ripple',
path: './build/',
filename: [ 'ripple-', '-core.js' ].join(pkg.version)
},
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
}, 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('bower', ['bower-build', 'bower-build-min', 'bower-build-debug', 'bower-version']);
gulp.task('lint', function() { gulp.task('lint', function() {
gulp.src('src/js/ripple/*.js') gulp.src('src/js/ripple/*.js')
.pipe(jshint()) .pipe(jshint())
@@ -117,4 +179,20 @@ gulp.task('watch', function() {
gulp.watch('src/js/ripple/*', [ 'build-debug' ]); gulp.watch('src/js/ripple/*', [ 'build-debug' ]);
}); });
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('default', [ 'concat-sjcl', 'build', 'build-debug', 'build-min' ]); gulp.task('default', [ 'concat-sjcl', 'build', 'build-debug', 'build-min' ]);

View File

@@ -1,3 +1,169 @@
##0.10.0
+ [Transaction changes](https://github.com/ripple/ripple-lib/pull/221)
+ **Important** `tef*` and `tel*` and errors will no longer be presented as
final. Rather than considering these errors final, ripple-lib will wait until
the `LastLedgerSequence` specified in the transaction is exceeded. This makes
failures more definitive, and ensures that no transaction will resubmit
indefinitely.
+ A new, final tej-class error is introduced to account for transactions that
are locally determined to have expired: `tejMaxLedger`.
+ [Allow per transaction fees to be set, `transaction.setFixedFee()`](https://github.com/ripple/ripple-lib/commit/9b22f279bcbe60ee6bcf4b7fa60a48e9c197a828)
+ [Improve memo support](https://github.com/ripple/ripple-lib/commit/1704ac4ae144c0ce54afad86f644c75a632080b1)
- Add `MemoFormat` property for memo
- Enforce `MemoFormat` and `MemoType` to be valid ASCII
- Support `text` and `json` MemoFormat
+ [Update jscl library](https://github.com/ripple/ripple-lib/commit/3204998fcb6f31d6c90532a737a4adb8a1e420f6)
- Improved entropy by taking advantage of platform crypto
- Use jscl's k256 curve instead of altering the c256 curve with k256 configuration
- **deprecated:** the c256 curve is linked to the k256 curve to provide backwards compatibility, this link will be removed in the future
+ [Fix empty queue check on reconnect](https://github.com/ripple/ripple-lib/commit/3c21994adcf72d1fbd87d453ceb917f9ad6df4ec)
##0.9.4
+ [Normalize offers from book_offers and transaction stream](https://github.com/ripple/ripple-lib/commit/86ed24b94cf7c8929c87db3a63e9bbea7f767e9c)
+ [Fix: Amount.to_human() precision rounding](https://github.com/ripple/ripple-lib/commit/e371cc2c3ceccb3c1cfdf18b98d80093147dd8b2)
+ [Fix: fractional drops in funded taker_pays setter](https://github.com/ripple/ripple-lib/commit/0d7fc0a573a144caac15dd13798b23eeb1f95fb4)
##0.9.3
+ [Change `presubmit` to emit immediately before transaction submit](https://github.com/ripple/ripple-lib/commit/7a1feaa89701bf861ab31ebd8ffdc8d8d1474e29)
+ [Add a "core" browser build of ripple-lib which has a subset of features and smaller file size](https://github.com/ripple/ripple-lib/pull/205)
+ [Update binformat with missing fields from rippled](https://github.com/ripple/ripple-lib/commit/cae980788efb00191bfd0988ed836d60cdf7a9a2)
+ [Wait for transaction validation before returning `tec` error](https://github.com/ripple/ripple-lib/commit/6bdd4b2670906588852fc4dda457607b4aac08e4)
+ [Change default `max_fee` on `Remote` to `1 XRP`](https://github.com/ripple/ripple-lib/commit/d6b1728c23ff85c3cc791bed6982a750641fd95f)
+ [Fix: Request ledger_accept should return the Remote](https://github.com/ripple/ripple-lib/pull/209)
##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 ##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 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.

View File

@@ -1,37 +1,36 @@
#The Ripple JavaScript Library #ripple-lib
A JavaScript API for interacting with Ripple in Node.js and the browser
[![Build Status](https://travis-ci.org/ripple/ripple-lib.svg?branch=develop)](https://travis-ci.org/ripple/ripple-lib) [![Coverage Status](https://coveralls.io/repos/ripple/ripple-lib/badge.png?branch=develop)](https://coveralls.io/r/ripple/ripple-lib?branch=develop) [![Build Status](https://travis-ci.org/ripple/ripple-lib.svg?branch=develop)](https://travis-ci.org/ripple/ripple-lib) [![Coverage Status](https://coveralls.io/repos/ripple/ripple-lib/badge.png?branch=develop)](https://coveralls.io/r/ripple/ripple-lib?branch=develop)
[![NPM](https://nodei.co/npm/ripple-lib.png)](https://www.npmjs.org/package/ripple-lib) [![NPM](https://nodei.co/npm/ripple-lib.png)](https://www.npmjs.org/package/ripple-lib)
`ripple-lib` connects to the Ripple network via the WebSocket protocol and runs in Node.js as well as in the browser. ###Features
###Use ripple-lib for: + 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
+ Connecting to a local or remote rippled in JavaScript (Node.js or browser) ###In this file
+ Issuing [rippled API](https://ripple.com/wiki/JSON_Messages) requests
+ Listening to events on the Ripple network (transaction, ledger, etc.)
+ Signing and submitting transactions to the Ripple network
###In this file: 1. [Installation](#installation)
2. [Quick start](#quick-start)
3. [Running tests](#running-tests)
1. Overview ###Additional documentation
2. [Getting `ripple-lib`](README.md#getting-ripple-lib)
3. [Quickstart](README.md#quickstart)
4. [Running tests](https://github.com/ripple/ripple-lib#running-tests)
###For additional documentation see: 1. [Guides](docs/GUIDES.md)
2. [API Reference](docs/REFERENCE.md)
3. [Wiki](https://ripple.com/wiki/Ripple_JavaScript_library)
1. [The `ripple-lib` Guides (docs/GUIDES.md)](docs/GUIDES.md) ###Also see
2. [The `ripple-lib` API Reference (docs/REFERENCE.md)](docs/REFERENCE.md)
3. https://ripple.com/wiki/Ripple_JavaScript_library
###Also see: + [The Ripple wiki](https://ripple.com/wiki)
+ [ripple.com](https://ripple.com)
+ https://ripple.com/wiki ##Installation
+ https://ripple.com
##Getting `ripple-lib`
**Via npm for Node.js** **Via npm for Node.js**
@@ -39,7 +38,18 @@
$ npm install ripple-lib $ npm install ripple-lib
``` ```
**Building ripple-lib for browser client** **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 for browser environments**
ripple-lib uses Gulp to generate browser builds. These steps will generate minified and non-minified builds of ripple-lib in the `build/` directory.
``` ```
$ git clone https://github.com/ripple/ripple-lib $ git clone https://github.com/ripple/ripple-lib
@@ -47,11 +57,15 @@
$ npm run build $ npm run build
``` ```
Then use the minified `build/ripple-*-min.js` in your webpage **Restricted browser builds**
##Quickstart You may generate browser builds that contain a subset of features. To do this, run `./node_modules/.bin/gulp build-<name>`
`Remote` ([remote.js](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/remote.js)) is the module responsible for managing connections to `rippled` servers: + `build-core` Contains the functionality to make requests and listen for events such as `ledgerClose`. Only `ripple.Remote` is currently exposed. Advanced features like transaction submission and orderbook tracking are excluded from this build.
##Quick start
`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 ```js
/* Loading ripple-lib with Node.js */ /* Loading ripple-lib with Node.js */
@@ -62,35 +76,24 @@ var Remote = require('ripple-lib').Remote;
var remote = new Remote({ var remote = new Remote({
// see the API Reference for available options // see the API Reference for available options
trusted: true, servers: [ 'wss://s1.ripple.com:443' ]
local_signing: true,
local_fee: true,
fee_cushion: 1.5,
servers: [
{
host: 's1.ripple.com'
, port: 443
, secure: true
}
]
}); });
remote.connect(function() { remote.connect(function() {
/* remote connected */ /* remote connected */
remote.requestServerInfo(function(err, info) {
// see the API Reference for available functions // process err and info
});
}); });
``` ```
See [The `ripple-lib` Guides](docs/GUIDES.md) and [The `ripple-lib` API Reference](docs/REFERENCE.md) for walkthroughs and details about all of the available functions and options.
##Running tests ##Running tests
1. Clone the repository 1. Clone the repository
2. `cd` into the repository and install dependencies with `npm install` 2. `cd` into the repository and install dependencies with `npm install`
3. `npm test` or `make test` or `node_modules\.bin\mocha test\*-test.js` 3. `npm test`
**Generating code coverage** **Generating code coverage**

View File

@@ -56,7 +56,7 @@ function ready() {
function print_usage() { function print_usage() {
console.log( console.log(
'Usage: rsign.js <secret> <json>\n\n', 'Usage: rsign.js <secret> <json>\n\n',
'Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC', 'Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC','\''+
JSON.stringify({ JSON.stringify({
TransactionType: 'Payment', TransactionType: 'Payment',
Account: 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi', Account: 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi',
@@ -64,7 +64,7 @@ function print_usage() {
Amount: '200000000', Amount: '200000000',
Fee: '10', Fee: '10',
Sequence: 1 Sequence: 1
}) })+'\''
); );
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,25 @@
#`ripple-lib` Guides #Guides
This file provides step-by-step walkthroughs for some of the most common usages of `ripple-lib`. This file provides step-by-step walkthroughs for some of the most common usages of `ripple-lib`.
###Guides in this document: ###In this document
1. [Connecting to the Ripple network with `Remote`](GUIDES.md#1-connecting-to-the-ripple-network-with-remote) 1. [Connecting to the Ripple network with `Remote`](GUIDES.md#connecting-to-the-ripple-network)
2. [Using `Remote` functions and `Request` objects](GUIDES.md#2-using-remote-functions-and-request-objects) 2. [Using `Remote` functions and `Request` objects](GUIDES.md#sending-rippled-API-requests)
3. [Submitting a payment to the network](GUIDES.md#3-submitting-a-payment-to-the-network) 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) * [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees)
4. [Submitting a trade offer to the network](GUIDES.md#4-submitting-a-trade-offer-to-the-network) 5. [Submitting a trade offer to the network](GUIDES.md#submitting-a-trade-offer-to-the-network)
5. [Listening to the network](GUIDES.md#5-listening-to-the-network)
###Also see
###Also see: 1. [The ripple-lib README](../README.md)
2. [The ripple-lib API Reference](REFERENCE.md)
1. [The `ripple-lib` README](../README.md) ##Connecting to the Ripple network
2. [The `ripple-lib` API Reference](REFERENCE.md)
##1. Connecting to the Ripple network with `Remote` 1. [Get ripple-lib](README.md#getting-ripple-lib)
2. Load the ripple-lib module into a Node.js file or webpage:
1. [Get `ripple-lib`](README.md#getting-ripple-lib)
2. Load the `ripple-lib` module into a Node.js file or webpage:
```js ```js
/* Loading ripple-lib with Node.js */ /* Loading ripple-lib with Node.js */
var Remote = require('ripple-lib').Remote; var Remote = require('ripple-lib').Remote;
@@ -30,39 +29,80 @@ This file provides step-by-step walkthroughs for some of the most common usages
``` ```
3. Create a new `Remote` and connect to the network: 3. Create a new `Remote` and connect to the network:
```js ```js
var options = {
trace : false,
trusted: true,
local_signing: true,
servers: [
{ host: 's-west.ripple.com', port: 443, secure: true }
]
}
var remote = new Remote({options}); var remote = new Remote({options});
remote.connect(function() { remote.connect(function(err, res) {
/* remote connected, use some remote functions here */ /* remote connected, use some remote functions here */
}); });
``` ```
__NOTE:__ See the API Reference for available [`Remote` options](REFERENCE.md#1-remote-options) __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. 4. You're connected! Read on to see what to do now.
##Generating a new Ripple Wallet
##2. Using `Remote` functions and `Request` objects ```js
var ripple = require('ripple-lib');
All `Remote` functions return a `Request` object. // subscribing to a server allows for more entropy
var remote = new ripple.Remote({
servers: [
{ host: 's1.ripple.com', port: 443, secure: true }
]
});
A `Request` is an `EventEmitter` so you can listen for success or failure events -- or, instead, you can provide a callback to the `Remote` function. remote.connect(function(err, res) {
/* remote connected */
});
Here is an example, using `request_server_info()`, of how `Remote` functions can be used with event listeners (the first code block) or with a callback (the second block): // Wait for randomness to have been added.
// The entropy of the random generator is increased
// by random data received from a rippled
remote.once('random', function(err, info) {
var wallet = ripple.Wallet.generate();
console.log(wallet);
// { address: 'rEf4sbVobiiDGExrNj2PkNHGMA8eS6jWh3',
// secret: 'shFh4a38EZpEdZxrLifEnVPAoBRce' }
});
```
+ Using a `Remote` function with `Request` event listeners:
##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 [requestServerInfo](https://ripple.com/wiki/JSON_Messages#server_info).
+ Constructing a `Request` with event listeners
```js ```js
var request = remote.request_server_info(); var request = remote.requestServerInfo();
request.on('success', function(res) {
request.on('success', function onSuccess(res) {
//handle success //handle success
}); });
request.on('error', function(err) {
request.on('error', function onError(err) {
//handle error //handle error
}); });
request.request(); // this triggers the request if it has not already been sent to the server
request.request();
``` ```
+ Using a `Remote` function with a callback: + Using a callback:
```js ```js
remote.request_server_info(function(err, res) { remote.request('server_info', function(err, res) {
if (err) { if (err) {
//handle error //handle error
} else { } else {
@@ -74,14 +114,69 @@ remote.request_server_info(function(err, res) {
__NOTE:__ See the API Reference for available [`Remote` functions](REFERENCE.md#2-remote-functions) __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 remote = new Remote({
// see the API Reference for available options
servers: [ 'wss://s1.ripple.com:443' ]
});
remote.connect(function() {
console.log('Remote connected');
var streams = [
'ledger',
'transactions'
];
var request = remote.requestSubscribe(streams);
request.on('error', function(error) {
console.log('request error: ', error);
});
##3. Submitting a payment to the network // the `ledger_closed` and `transaction` will come in on the remote
// since the request for subscribe is finalized after the success return
// the streaming events will still come in, but not on the initial request
remote.on('ledger_closed', function(ledger) {
console.log('ledger_closed: ', JSON.stringify(ledger, null, 2));
});
remote.on('transaction', function(transaction) {
console.log('transaction: ', JSON.stringify(transaction, null, 2));
});
remote.on('error', function(error) {
console.log('remote error: ', error);
});
// fire the request
request.request();
});
});
```
* 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. 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 ```js
/* Loading ripple-lib Remote and Amount modules in Node.js */ /* Loading ripple-lib Remote and Amount modules in Node.js */
var Remote = require('ripple-lib').Remote; var Remote = require('ripple-lib').Remote;
var Amount = require('ripple-lib').Amount; var Amount = require('ripple-lib').Amount;
@@ -97,13 +192,11 @@ var AMOUNT = Amount.from_human('1XRP');
var remote = new Remote({ /* Remote options */ }); var remote = new Remote({ /* Remote options */ });
remote.connect(function() { remote.connect(function() {
remote.set_secret(MY_ADDRESS, MY_SECRET); remote.setSecret(MY_ADDRESS, MY_SECRET);
var transaction = remote.transaction(); var transaction = remote.createTransaction('Payment', {
account: MY_ADDRESS,
transaction.payment({ destination: RECIPIENT,
from: MY_ADDRESS,
to: RECIPIENT,
amount: AMOUNT amount: AMOUNT
}); });
@@ -124,12 +217,12 @@ Since the fee required for a transaction may change between the time when the or
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. 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
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: Submitting a trade offer to the network is similar to submitting a payment transaction. Here is an example offering to sell 1 USD in exchange for 100 XRP:
```js ```js
/* Loading ripple-lib Remote and Amount modules in Node.js */ /* Loading ripple-lib Remote and Amount modules in Node.js */
var Remote = require('ripple-lib').Remote; var Remote = require('ripple-lib').Remote;
var Amount = require('ripple-lib').Amount; var Amount = require('ripple-lib').Amount;
@@ -139,27 +232,17 @@ var Amount = require('ripple-lib').Amount;
var MY_ADDRESS = 'rrrMyAddress'; var MY_ADDRESS = 'rrrMyAddress';
var MY_SECRET = 'secret'; var MY_SECRET = 'secret';
var GATEWAY = 'rrrGateWay';
var BUY_AMOUNT = Amount.from_human('100XRP');
var SELL_AMOUNT = Amount.from_human('1USD');
// EXPIRATION must be a Date object, leave undefined to submit offer that won't expire
var now = new Date();
var tomorrow = new Date(now.getTime() + (24 * 60 * 60 * 1000));
var EXPIRATION = tomorrow;
var remote = new Remote({ /* Remote options */ }); var remote = new Remote({ /* Remote options */ });
remote.connect(function() { remote.connect(function() {
remote.set_secret(MY_ADDRESS, MY_SECRET); remote.setSecret(MY_ADDRESS, MY_SECRET);
var transaction = remote.transaction(); var transaction = remote.createTransaction('OfferCreate', {
account: MY_ADDRESS,
transaction.offer_create({ taker_pays: '100',
from: MY_ADDRESS, taker_gets: '1/USD/' + GATEWAY
buy: BUY_AMOUNT,
sell: SELL_AMOUNT,
expiration: EXPIRATION
}); });
transaction.submit(function(err, res) { transaction.submit(function(err, res) {
@@ -167,35 +250,3 @@ remote.connect(function() {
}); });
}); });
``` ```
##5. Listening to the network
In some (relatively rare) cases you may want to subscribe to the network event feed and listen for transactions and the ledger closings. [Ripple.com](http://www.ripple.com) uses this feature of `ripple-lib` to display the live feed on the top of each page and the ledger closing visualization on the [Developers page](http://ripple.com/devs).
```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() {
remote.on('transaction_all', transactionListener);
remote.on('ledger_closed', ledgerListener);
});
function transactionListener (transaction_data) {
// handle transaction_data
// see https://ripple.com/wiki/RPC_API#transactions_stream_messages for the format of transaction_data
}
function ledgerListener (ledger_data) {
// handle ledger_data
// see https://ripple.com/wiki/RPC_API#ledger_stream_messages for the format of ledger_data
}
```
* https://ripple.com/wiki/RPC_API#transactions_stream_messages
* https://ripple.com/wiki/RPC_API#ledger_stream_messages

View File

@@ -1,28 +1,26 @@
#`ripple-lib` API Reference #API Reference
__(More examples coming soon!)__ __(More examples coming soon!)__
###In this document: ###In this document:
1. [`Remote` options](REFERENCE.md#1-remote-options) 1. [`Remote` options](REFERENCE.md#remote-options)
2. [`Remote` functions](REFERENCE.md#2-remote-functions) 2. [`Request` constructors](REFERENCE.md#request-constructor-functions)
+ [Server info functions](REFERENCE.md#server-info-functions) + [Server requests](REFERENCE.md#server-requests)
+ [Ledger query functions](REFERENCE.md#ledger-query-functions) + [Ledger requests](REFERENCE.md#ledger-requests)
+ [Transaction query functions](REFERENCE.md#transaction-query-functions) + [Transaction requests](REFERENCE.md#transaction-requests)
+ [Account query functions](REFERENCE.md#account-query-functions) + [Account requests](REFERENCE.md#account-requests)
+ [Order book query functions](REFERENCE.md#order-book-query-functions) + [Orderbook requests](REFERENCE.md#orderbook-requests)
+ [Transaction submission functions](REFERENCE.md#transaction-submission-functions) + [Transaction requests](REFERENCE.md#transaction-requests)
3. [`Transaction` events](REFERENCE.md#3-transaction-events) 3. [`Transaction` constructors](REFERENCE.md#transaction-constructors)
4. [`Amount` objects](REFERENCE.md#4-amount-objects) + [Transaction events](REFERENCE.md#transaction-events)
###Also see: ###Also see:
1. [The `ripple-lib` README](../README.md) 1. [The ripple-lib README](../README.md)
2. [The `ripple-lib` GUIDES](GUIDES.md) 2. [The ripple-lib GUIDES](GUIDES.md)a
#Remote options
#1. `Remote` options
```js ```js
/* Loading ripple-lib with Node.js */ /* Loading ripple-lib with Node.js */
@@ -31,102 +29,116 @@ var Remote = require('ripple-lib').Remote;
/* Loading ripple-lib in a webpage */ /* Loading ripple-lib in a webpage */
// var Remote = ripple.Remote; // var Remote = ripple.Remote;
var remote = new Remote({options}); var options = { };
var remote = new Remote(options);
``` ```
A new `Remote` can be created with the following options: A new `Remote` can be created with the following options:
+ `trace` Log all of the events emitted (boolean) + `trace` *boolean default: false* Log all of the events emitted
+ `max_listeners` Set maxListeners for remote; prevents EventEmitter warnings (number) + `max_listeners` *number default: 0* Set maxListeners for servers
+ `connection_offset` Connect to remote servers on supplied interval (number in seconds) + `trusted` *boolean default: false*, if remote is trusted (boolean)
+ `trusted` truthy, if remote is trusted (boolean) + `local_signing` *boolean default: true*
+ `local_fee` Set whether the transaction fee range will be set locally (boolean, default is true, see [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees)) + `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` Extra fee multiplier to account for async fee changes (number, e.g. 1.5, 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` Maximum acceptable transaction fee (number in [XRP drops](https://ripple.com/wiki/Ripple_credits#Notes_on_drops), 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 of server objects of the following form: + `servers` *array* Array of server objects of the following form:
```js ```js
{ {
host: <string> host: <string>,
, port: <number> port: <number>,
, secure: <boolean> secure: <boolean>
} }
``` ```
+ `local_signing`
#2. `Remote` functions or
##Server info functions
**[requestServerInfo([callback])](https://ripple.com/wiki/RPC_API#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.set_server`. Example:
```js ```js
var request = remote.request_server_info(); 'wss://host:port'
request.set_server('my.hostname');
request.callback(function(err, res) {
});
request.request();
``` ```
**[requestUnlList([callback])](https://ripple.com/wiki/RPC_API#unl_list)** #Request constructor functions
**[requestUnlAdd(addr, comment, [callback])](https://ripple.com/wiki/RPC_API#unl_add)** Some requests have helper methods to construct the requests object and set properties on the message object. These will often be the more used requests and the helper methods is the preferred way of constructing these requests.
Other request can still be made, but the type will have to be passed in directly to request constructor. See examples below.
**[requestUnlDelete(node, [callback])](https://ripple.com/wiki/RPC_API#unl_delete)** If the method is camelCased and starts with `request`, it's a helper method that wraps the request constructor.
**[requestPeers([callback])](https://ripple.com/wiki/RPC_API#peers)** ##Server requests
**[requestServerInfo([callback])](https://ripple.com/wiki/JSON_Messages#server_info)**
**[requestConnect(ip, port, [callback])](https://ripple.com/wiki/RPC_API#connect)** 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.requestServerInfo();
request.setServer('wss://s1.ripple.com');
request.request(function(err, res) {
});
```
**[requestPeers([callback])](https://ripple.com/wiki/JSON_Messages#peers)**
**[requestConnect(ip, port, [callback])](https://ripple.com/wiki/JSON_Messages#connect)**
**[unl_list([callback])](https://ripple.com/wiki/JSON_Messages#unl_list)**
```js
var request = remote.request('un_list');
request.setServer('wss://s1.ripple.com');
request.request(function(err, res) {
});
```
**[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)**
##Ledger query functions ##Ledger requests
**[requestLedger(ledger, [opts], [callback])](https://ripple.com/wiki/RPC_API#ledger)** **[requestLedger([opts], [callback])](https://ripple.com/wiki/JSON_Messages#ledger)**
**requestLedgerHeader([callback])** **[requestLedgerHeader([callback])](https://wiki.ripple.com/JSON_Messages#ledger_data)**
**[requestLedgerCurrent([callback])](https://ripple.com/wiki/RPC_API#ledger_current)** **[requestLedgerCurrent([callback])](https://ripple.com/wiki/JSON_Messages#ledger_current)**
**[requestLedgerEntry(type, [callback])](https://ripple.com/wiki/RPC_API#ledger_entry)** **[requestLedgerEntry(type, [callback])](https://ripple.com/wiki/JSON_Messages#ledger_entry)**
**[requestSubscribe(streams, [callback])](https://ripple.com/wiki/RPC_API#subscribe)** **[requestSubscribe([streams], [callback])](https://ripple.com/wiki/JSON_Messages#subscribe)**
Start receiving selected streams from the server. Start receiving selected streams from the server.
**[requestUnsubscribe(streams, [callback])](https://ripple.com/wiki/RPC_API#unsubscribe)** **[requestUnsubscribe([streams], [callback])](https://ripple.com/wiki/JSON_Messages#unsubscribe)**
Stop receiving selected streams from the server. Stop receiving selected streams from the server.
##Account requests
**[requestAccountInfo(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_info)**
##Transaction query functions
**[requestTransactionEntry(hash, [ledger_hash], [callback])](https://ripple.com/wiki/RPC_API#transaction_entry)**
Searches a particular ledger for a transaction hash. Default ledger is the open ledger.
**[requestTx(hash, [callback])](https://ripple.com/wiki/RPC_API#tx)**
Searches ledger history for validated transaction hashes.
##Account query functions
**[requestAccountInfo(account, [callback])](https://ripple.com/wiki/RPC_API#account_info)**
Return information about the specified account. Return information about the specified account.
``` ```
var options = {
account: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
ledger: 'validated'
};
var request = remote.requestAccountInfo(options, function(err, info) {
/* process info */
});
// response
{ {
ledger_current_index: <number>, ledger_current_index: <number>,
account_data: { account_data: {
@@ -143,13 +155,35 @@ Return information about the specified account.
} }
``` ```
**[requestAccountLines(accountID, account_index, current, [callback])](https://ripple.com/wiki/RPC_API#account_lines)** **[requestAccountLines(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_lines)**
**[requestAccountOffers(accountID, account_index, current, [callback])](https://ripple.com/wiki/RPC_API#account_offers)** **[requestAccountOffers(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_offers)**
Return the specified account's outstanding offers. Return the specified account's outstanding offers.
**[requestAccountTx(opts, [callback])](https://ripple.com/wiki/RPC_API#account_tx)** Requests for both `account_lines` and `account_offers` support paging. The amount of results per response can be configured with the `limit`.
The responses can be paged through by using the `marker`.
```
// 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);
```
**[requestAccountTransactions(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_tx)**
Fetch a list of transactions that applied to this account. Fetch a list of transactions that applied to this account.
@@ -167,92 +201,154 @@ Options:
+ `fwd_marker` + `fwd_marker`
+ `rev_marker` + `rev_marker`
**[requestWalletAccounts(seed, [callback])](https://ripple.com/wiki/RPC_API#wallet_accounts)** **[requestWalletAccounts(seed, [callback])](https://ripple.com/wiki/JSON_Messages#wallet_accounts)**
Return a list of accounts for a wallet. Return a list of accounts for a wallet. *Requires trusted remote*
+ requires trusted remote **requestAccountBalance(account, [ledger], [callback])**
**requestAccountBalance(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. Get the balance for an account. Returns an [Amount](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/amount.js) object.
**requestAccountFlags(account, current, [callback])** **requestAccountFlags(account, [ledger], [callback])**
Return the flags for an account. Return the flags for an account.
**requestOwnerCount(account, current, [callback])** **requestOwnerCount(account, [ledger], [callback])**
Return the owner count for an account. Return the owner count for an account.
**requestRippleBalance(account, issuer, currency, current, [callback])** **requestRippleBalance(account, issuer, currency, [ledger], [callback])**
Return a request to get a ripple balance Return a request to get a ripple balance
##Orderbook requests
**[requestBookOffers(options, [callback])](https://ripple.com/wiki/JSON_Messages#book_offers)**
Return the offers for an order book, also called a *snapshot*
##Order book query functions
**[requestBookOffers(gets, pays, taker, [callback])](https://ripple.com/wiki/RPC_API#book_offers)**
Return the offers for an order book as one or more pages.
```js ```js
var request = remote.request_book_offers({ var options = {
gets: { gets: {
'currency':'XRP' issuer: < issuer >,
currency: < currency >
}, },
pays: { pays: {
'currency':'USD', issuer: < issuer >,
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' currency: < currency >
} },
}); limit: < limit >
};
request.request(); var request = remote.requestBookOffers(options);
request.request(function(err, offers) {
//handle offers
});
``` ```
##Transaction requests
**[requestTransactionEntry(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.
##Transaction submission functions **[requestTransaction(hash, [callback])](https://ripple.com/wiki/JSON_Messages#tx)**
**[requestSign(secret, tx_json, [callback])](https://ripple.com/wiki/RPC_API#sign)** Searches ledger history for validated transaction hashes.
Sign a transaction. **[requestSign(secret, tx_json, [callback])](https://ripple.com/wiki/JSON_Messages#sign)**
+ requires trusted remote Sign a transaction. *Requires trusted remote*
**[requestSubmit([callback])](https://ripple.com/wiki/RPC_API#submit)** **[requestSubmit([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. 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.
**[pathFind(src_account, dst_account, dst_amount, src_currencies)](https://ripple.com/wiki/JSON_Messages#path_find)**
**[requestRipplePathFind(src_account, dst_account, dst_amount, src_currencies, [callback])](https://ripple.com/wiki/RPC_API#path_find)** #Transaction constructors
Use `remote.createTransaction('TransactionType', [options])` to construct a transaction. To submit, use `transaction.submit([callback])`.
**transaction([destination], [source], [amount], [callback])** **Payment**
Returns a [Transaction](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/transaction.js) object ```js
var transaction = remote.createTransaction('Payment', {
account: MY_ADDRESS,
destination: DEST_ADDRESS,
amount: AMOUNT
});
```
**AccountSet**
#3. Transaction events ```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. [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. + `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. + `error` Transaction has erred. This event is a final state.
+ `success` Transaction succeeded. 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. + `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. + `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. + `timeout` Transaction submission timed out. The transaction will be resubmitted.
+ `resubmit` Transaction is beginning resubmission.
+ `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. + `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`. + `abort` Transaction has been aborted. Transactions are only aborted by manual calls to `#abort`.
+ `missing` Four ledgers have closed without detecting validated transaction + `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. + `lost` Eight ledgers have closed without detecting validated transaction. Consider the transaction lost and err/finalize.
##Complete payment example
#4. Amount objects ```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 Coming Soon

155
npm-shrinkwrap.json generated Normal file
View File

@@ -0,0 +1,155 @@
{
"name": "ripple-lib",
"version": "0.10.0",
"dependencies": {
"async": {
"version": "0.8.0",
"from": "async@>=0.8.0 <0.9.0",
"resolved": "https://registry.npmjs.org/async/-/async-0.8.0.tgz"
},
"extend": {
"version": "1.2.1",
"from": "extend@>=1.2.1 <1.3.0",
"resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz"
},
"lru-cache": {
"version": "2.5.0",
"from": "lru-cache@>=2.5.0 <2.6.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.0.tgz"
},
"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",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-0.18.2.tgz",
"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",
"resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz",
"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.7",
"from": "combined-stream@>=0.0.4 <0.1.0",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz",
"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",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz"
}
}
},
"readable-stream": {
"version": "1.0.27-1",
"from": "readable-stream@1.0.27-1",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.27-1.tgz",
"dependencies": {
"core-util-is": {
"version": "1.0.1",
"from": "core-util-is@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
},
"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",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
},
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <2.1.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
}
}
},
"ws": {
"version": "0.4.32",
"from": "ws@>=0.4.31 <0.5.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-0.4.32.tgz",
"dependencies": {
"commander": {
"version": "2.1.0",
"from": "commander@>=2.1.0 <2.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz"
},
"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"
}
}
}
}
}

View File

@@ -1,7 +1,7 @@
{ {
"name": "ripple-lib", "name": "ripple-lib",
"version": "0.7.39", "version": "0.10.0",
"description": "Ripple JavaScript client library", "description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [ "files": [
"src/js/*", "src/js/*",
"bin/*", "bin/*",
@@ -19,7 +19,8 @@
"ws": "~0.4.31", "ws": "~0.4.31",
"extend": "~1.2.1", "extend": "~1.2.1",
"lru-cache": "~2.5.0", "lru-cache": "~2.5.0",
"superagent": "^0.18.0" "superagent": "^0.18.0",
"ripple-wallet-generator": "1.0.1"
}, },
"devDependencies": { "devDependencies": {
"mocha": "~1.14.0", "mocha": "~1.14.0",
@@ -28,16 +29,18 @@
"gulp-jshint": "~1.5.5", "gulp-jshint": "~1.5.5",
"gulp-uglify": "~0.3.0", "gulp-uglify": "~0.3.0",
"gulp-rename": "~1.2.0", "gulp-rename": "~1.2.0",
"gulp-bump": "~0.1.10",
"webpack": "~1.1.11", "webpack": "~1.1.11",
"map-stream": "~0.1.0", "map-stream": "~0.1.0",
"istanbul": "~0.2.10", "istanbul": "~0.2.10",
"coveralls": "~2.10.0", "coveralls": "~2.10.0",
"nock": "^0.34.1" "nock": "^0.34.1",
"yargs": "~1.3.1"
}, },
"scripts": { "scripts": {
"build": "node_modules/.bin/gulp", "build": "node_modules/.bin/gulp",
"pretest": "node_modules/.bin/gulp concat-sjcl", "pretest": "node_modules/.bin/gulp concat-sjcl",
"test": "./node_modules/.bin/istanbul test -x build/sjcl.js -x src/js/jsbn/* ./node_modules/.bin/_mocha -- --reporter spec test/*-test.js", "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" "coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls"
}, },
"repository": { "repository": {

43
scripts/publish Normal file
View 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
View 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
View 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 ..

View File

@@ -132,7 +132,7 @@ Account.prototype.isValid = function() {
*/ */
Account.prototype.getInfo = function(callback) { Account.prototype.getInfo = function(callback) {
return this._remote.request_account_info(this._account_id, callback); return this._remote.requestAccountInfo({account: this._account_id}, callback);
}; };
/** /**
@@ -174,8 +174,8 @@ Account.prototype.getNextSequence = function(callback) {
function accountInfo(err, info) { function accountInfo(err, info) {
if (isNotFound(err)) { if (isNotFound(err)) {
// New accounts will start out as sequence zero // New accounts will start out as sequence one
callback(null, 0); callback(null, 1);
} else if (err) { } else if (err) {
callback(err); callback(err);
} else { } else {
@@ -211,7 +211,7 @@ Account.prototype.lines = function(callback) {
} }
} }
this._remote.requestAccountLines(this._account_id, accountLines); this._remote.requestAccountLines({account: this._account_id}, accountLines);
return this; return this;
}; };

View File

@@ -12,7 +12,25 @@ var UInt160 = require('./uint160').UInt160;
var Seed = require('./seed').Seed; var Seed = require('./seed').Seed;
var Currency = require('./currency').Currency; var Currency = require('./currency').Currency;
var consts = exports.consts = { //
// Amount class in the style of Java's BigInteger class
// http://docs.oracle.com/javase/1.3/docs/api/java/math/BigInteger.html
//
function Amount() {
// Json format:
// integer : XRP
// { 'value' : ..., 'currency' : ..., 'issuer' : ...}
this._value = new BigInteger(); // NaN for bad value. Always positive.
this._offset = 0; // Always 0 for XRP.
this._is_native = true; // Default to XRP. Only valid if value is not NaN.
this._is_negative = false;
this._currency = new Currency();
this._issuer = new UInt160();
};
var consts = {
currency_xns: 0, currency_xns: 0,
currency_one: 1, currency_one: 1,
xns_precision: 6, xns_precision: 6,
@@ -33,26 +51,19 @@ var consts = exports.consts = {
cMinOffset: -96, cMinOffset: -96,
cMaxOffset: 80, cMaxOffset: 80,
// Maximum possible amount for non-XRP currencies using the maximum mantissa
// with maximum exponent. Corresponds to hex 0xEC6386F26FC0FFFF.
max_value: '9999999999999999e80',
// Minimum possible amount for non-XRP currencies.
min_value: '-1000000000000000e-96'
}; };
// Add constants to Amount class
extend(Amount, consts);
// // DEPRECATED: Use Amount instead, e.g. Amount.currency_xns
// Amount class in the style of Java's BigInteger class exports.consts = consts;
// http://docs.oracle.com/javase/1.3/docs/api/java/math/BigInteger.html
//
function Amount() {
// Json format:
// integer : XRP
// { 'value' : ..., 'currency' : ..., 'issuer' : ...}
this._value = new BigInteger(); // NaN for bad value. Always positive.
this._offset = 0; // Always 0 for XRP.
this._is_native = true; // Default to XRP. Only valid if value is not NaN.
this._is_negative = false;
this._currency = new Currency();
this._issuer = new UInt160();
};
// Given '100/USD/mtgox' return the a string with mtgox remapped. // Given '100/USD/mtgox' return the a string with mtgox remapped.
Amount.text_full_rewrite = function(j) { Amount.text_full_rewrite = function(j) {
@@ -132,12 +143,12 @@ Amount.prototype.add = function(v) {
var o2 = v._offset; var o2 = v._offset;
while (o1 < o2) { while (o1 < o2) {
v1 = v1.divide(consts.bi_10); v1 = v1.divide(Amount.bi_10);
o1 += 1; o1 += 1;
} }
while (o2 < o1) { while (o2 < o1) {
v2 = v2.divide(consts.bi_10); v2 = v2.divide(Amount.bi_10);
o2 += 1; o2 += 1;
} }
@@ -160,13 +171,245 @@ Amount.prototype.add = function(v) {
return result; return result;
}; };
// Result in terms of this currency and issuer.
Amount.prototype.subtract = function(v) {
// Correctness over speed, less code has less bugs, reuse add code.
return this.add(Amount.from_json(v).negate());
};
// Result in terms of this' currency and issuer.
// XXX Diverges from cpp.
Amount.prototype.multiply = function(v) {
var result;
v = Amount.from_json(v);
if (this.is_zero()) {
result = this;
} else if (v.is_zero()) {
result = this.clone();
result._value = BigInteger.ZERO;
} else {
var v1 = this._value;
var o1 = this._offset;
var v2 = v._value;
var o2 = v._offset;
if (this.is_native()) {
while (v1.compareTo(Amount.bi_man_min_value) < 0) {
v1 = v1.multiply(Amount.bi_10);
o1 -= 1;
}
}
if (v.is_native()) {
while (v2.compareTo(Amount.bi_man_min_value) < 0) {
v2 = v2.multiply(Amount.bi_10);
o2 -= 1;
}
}
result = new Amount();
result._offset = o1 + o2 + 14;
result._value = v1.multiply(v2).divide(Amount.bi_1e14).add(Amount.bi_7);
result._is_native = this._is_native;
result._is_negative = this._is_negative !== v._is_negative;
result._currency = this._currency;
result._issuer = this._issuer;
result.canonicalize();
}
return result;
};
// Result in terms of this' currency and issuer.
Amount.prototype.divide = function(d) {
var result;
d = Amount.from_json(d);
if (d.is_zero()) {
throw new Error('divide by zero');
}
if (this.is_zero()) {
result = this;
} else if (!this.is_valid()) {
throw new Error('Invalid dividend');
} else if (!d.is_valid()) {
throw new Error('Invalid divisor');
} else {
var _n = this;
if (_n.is_native()) {
_n = _n.clone();
while (_n._value.compareTo(Amount.bi_man_min_value) < 0) {
_n._value = _n._value.multiply(Amount.bi_10);
_n._offset -= 1;
}
}
var _d = d;
if (_d.is_native()) {
_d = _d.clone();
while (_d._value.compareTo(Amount.bi_man_min_value) < 0) {
_d._value = _d._value.multiply(Amount.bi_10);
_d._offset -= 1;
}
}
result = new Amount();
result._offset = _n._offset - _d._offset - 17;
result._value = _n._value.multiply(Amount.bi_1e17).divide(_d._value).add(Amount.bi_5);
result._is_native = _n._is_native;
result._is_negative = _n._is_negative !== _d._is_negative;
result._currency = _n._currency;
result._issuer = _n._issuer;
result.canonicalize();
}
return result;
};
/**
* This function calculates a ratio - such as a price - between two Amount
* objects.
*
* The return value will have the same type (currency) as the numerator. This is
* a simplification, which should be sane in most cases. For example, a USD/XRP
* price would be rendered as USD.
*
* @example
* var price = buy_amount.ratio_human(sell_amount);
*
* @this {Amount} The numerator (top half) of the fraction.
* @param {Amount} denominator The denominator (bottom half) of the fraction.
* @param opts Options for the calculation.
* @param opts.reference_date {Date|Number} Date based on which demurrage/interest
* should be applied. Can be given as JavaScript Date or int for Ripple epoch.
* @return {Amount} The resulting ratio. Unit will be the same as numerator.
*/
Amount.prototype.ratio_human = function(denominator, opts) {
opts = extend({ }, opts);
var numerator = this;
if (typeof denominator === 'number' && parseInt(denominator, 10) === denominator) {
// Special handling of integer arguments
denominator = Amount.from_json(String(denominator) + '.0');
} else {
denominator = Amount.from_json(denominator);
}
denominator = Amount.from_json(denominator);
// If either operand is NaN, the result is NaN.
if (!numerator.is_valid() || !denominator.is_valid()) {
return Amount.NaN();
}
if (denominator.is_zero()) {
return Amount.NaN();
}
// Apply interest/demurrage
//
// We only need to apply it to the second factor, because the currency unit of
// the first factor will carry over into the result.
if (opts.reference_date) {
denominator = denominator.applyInterest(opts.reference_date);
}
// Special case: The denominator is a native (XRP) amount.
//
// In that case, it's going to be expressed as base units (1 XRP =
// 10^xns_precision base units).
//
// However, the unit of the denominator is lost, so when the resulting ratio
// is printed, the ratio is going to be too small by a factor of
// 10^xns_precision.
//
// To compensate, we multiply the numerator by 10^xns_precision.
if (denominator._is_native) {
numerator = numerator.clone();
numerator._value = numerator._value.multiply(Amount.bi_xns_unit);
numerator.canonicalize();
}
return numerator.divide(denominator);
};
/**
* Calculate a product of two amounts.
*
* This function allows you to calculate a product between two amounts which
* retains XRPs human/external interpretation (i.e. 1 XRP = 1,000,000 base
* units).
*
* Intended use is to calculate something like: 10 USD * 10 XRP/USD = 100 XRP
*
* @example
* var sell_amount = buy_amount.product_human(price);
*
* @see Amount#ratio_human
*
* @this {Amount} The first factor of the product.
* @param {Amount} factor The second factor of the product.
* @param opts Options for the calculation.
* @param opts.reference_date {Date|Number} Date based on which demurrage/interest
* should be applied. Can be given as JavaScript Date or int for Ripple epoch.
* @return {Amount} The product. Unit will be the same as the first factor.
*/
Amount.prototype.product_human = function(factor, opts) {
opts = opts || {};
if (typeof factor === 'number' && parseInt(factor, 10) === factor) {
// Special handling of integer arguments
factor = Amount.from_json(String(factor) + '.0');
} else {
factor = Amount.from_json(factor);
}
// If either operand is NaN, the result is NaN.
if (!this.is_valid() || !factor.is_valid()) {
return Amount.NaN();
}
// Apply interest/demurrage
//
// We only need to apply it to the second factor, because the currency unit of
// the first factor will carry over into the result.
if (opts.reference_date) {
factor = factor.applyInterest(opts.reference_date);
}
var product = this.multiply(factor);
// Special case: The second factor is a native (XRP) amount expressed as base
// units (1 XRP = 10^xns_precision base units).
//
// See also Amount#ratio_human.
if (factor._is_native) {
product._value = product._value.divide(Amount.bi_xns_unit);
product.canonicalize();
}
return product;
};
/** /**
* Turn this amount into its inverse. * Turn this amount into its inverse.
* *
* @private * @private
*/ */
Amount.prototype._invert = function() { Amount.prototype._invert = function() {
this._value = consts.bi_1e32.divide(this._value); this._value = Amount.bi_1e32.divide(this._value);
this._offset = -32 - this._offset; this._offset = -32 - this._offset;
this.canonicalize(); this.canonicalize();
@@ -183,6 +426,33 @@ Amount.prototype.invert = function() {
return this.copy()._invert(); return this.copy()._invert();
}; };
/**
* Canonicalize amount value
*
* Mirrors rippled's internal Amount representation
* From https://github.com/ripple/rippled/blob/develop/src/ripple/data/protocol/STAmount.h#L31-L40
*
* Internal form:
* 1: If amount is zero, then value is zero and offset is -100
* 2: Otherwise:
* legal offset range is -96 to +80 inclusive
* value range is 10^15 to (10^16 - 1) inclusive
* amount = value * [10 ^ offset]
*
* -------------------
*
* The amount can be epxresses as A x 10^B
* Where:
* - A must be an integer between 10^15 and (10^16)-1 inclusive
* - B must be between -96 and 80 inclusive
*
* This results
* - minumum: 10^15 x 10^-96 -> 10^-81 -> -1e-81
* - maximum: (10^16)-1 x 10^80 -> 9999999999999999e80
*
* @returns {Amount}
* @throws {Error} if offset exceeds legal ranges, meaning the amount value is bigger than supported
*/
Amount.prototype.canonicalize = function() { Amount.prototype.canonicalize = function() {
if (!(this._value instanceof BigInteger)) { if (!(this._value instanceof BigInteger)) {
// NaN. // NaN.
@@ -196,34 +466,43 @@ Amount.prototype.canonicalize = function() {
// Normalize _offset to 0. // Normalize _offset to 0.
while (this._offset < 0) { while (this._offset < 0) {
this._value = this._value.divide(consts.bi_10); this._value = this._value.divide(Amount.bi_10);
this._offset += 1; this._offset += 1;
} }
while (this._offset > 0) { while (this._offset > 0) {
this._value = this._value.multiply(consts.bi_10); this._value = this._value.multiply(Amount.bi_10);
this._offset -= 1; this._offset -= 1;
} }
} }
// XXX Make sure not bigger than supported. Throw if so.
} else if (this.is_zero()) { } else if (this.is_zero()) {
this._offset = -100; this._offset = Amount.cMinOffset;
this._is_negative = false; this._is_negative = false;
} else { } else {
// Normalize mantissa to valid range. // Normalize mantissa to valid range.
while (this._value.compareTo(consts.bi_man_min_value) < 0) { while (this._value.compareTo(Amount.bi_man_min_value) < 0) {
this._value = this._value.multiply(consts.bi_10); this._value = this._value.multiply(Amount.bi_10);
this._offset -= 1; this._offset -= 1;
} }
while (this._value.compareTo(consts.bi_man_max_value) > 0) { while (this._value.compareTo(Amount.bi_man_max_value) > 0) {
this._value = this._value.divide(consts.bi_10); this._value = this._value.divide(Amount.bi_10);
this._offset += 1; this._offset += 1;
} }
} }
// Make sure not bigger than supported. Throw if so.
if (this.is_negative() && this._offset < Amount.cMinOffset) {
throw new Error('Exceeding min value of ' + Amount.min_value);
}
// Make sure not smaller than supported. Throw if so.
if (!this.is_negative() && this._offset > Amount.cMaxOffset) {
throw new Error('Exceeding max value of ' + Amount.max_value);
}
return this; return this;
}; };
@@ -234,6 +513,8 @@ Amount.prototype.clone = function(negate) {
Amount.prototype.compareTo = function(v) { Amount.prototype.compareTo = function(v) {
var result; var result;
v = Amount.from_json(v);
if (!this.is_comparable(v)) { if (!this.is_comparable(v)) {
result = Amount.NaN(); result = Amount.NaN();
} else if (this._is_negative !== v._is_negative) { } else if (this._is_negative !== v._is_negative) {
@@ -296,9 +577,7 @@ Amount.prototype.equals = function(d, ignore_issuer) {
return this.equals(Amount.from_json(d)); return this.equals(Amount.from_json(d));
} }
var result = true; var result = !((!this.is_valid() || !d.is_valid())
result = !((!this.is_valid() || !d.is_valid())
|| (this._is_native !== d._is_native) || (this._is_native !== d._is_native)
|| (!this._value.equals(d._value) || this._offset !== d._offset) || (!this._value.equals(d._value) || this._offset !== d._offset)
|| (this._is_negative !== d._is_negative) || (this._is_negative !== d._is_negative)
@@ -307,181 +586,6 @@ Amount.prototype.equals = function(d, ignore_issuer) {
return result; return result;
}; };
// Result in terms of this' currency and issuer.
Amount.prototype.divide = function(d) {
var result;
if (d.is_zero()) {
throw new Error('divide by zero');
}
if (this.is_zero()) {
result = this;
} else if (!this.is_valid()) {
throw new Error('Invalid dividend');
} else if (!d.is_valid()) {
throw new Error('Invalid divisor');
} else {
var _n = this;
if (_n.is_native()) {
_n = _n.clone();
while (_n._value.compareTo(consts.bi_man_min_value) < 0) {
_n._value = _n._value.multiply(consts.bi_10);
_n._offset -= 1;
}
}
var _d = d;
if (_d.is_native()) {
_d = _d.clone();
while (_d._value.compareTo(consts.bi_man_min_value) < 0) {
_d._value = _d._value.multiply(consts.bi_10);
_d._offset -= 1;
}
}
result = new Amount();
result._offset = _n._offset - _d._offset - 17;
result._value = _n._value.multiply(consts.bi_1e17).divide(_d._value).add(consts.bi_5);
result._is_native = _n._is_native;
result._is_negative = _n._is_negative !== _d._is_negative;
result._currency = _n._currency;
result._issuer = _n._issuer;
result.canonicalize();
}
return result;
};
/**
* Calculate a ratio between two amounts.
*
* This function calculates a ratio - such as a price - between two Amount
* objects.
*
* The return value will have the same type (currency) as the numerator. This is
* a simplification, which should be sane in most cases. For example, a USD/XRP
* price would be rendered as USD.
*
* @example
* var price = buy_amount.ratio_human(sell_amount);
*
* @this {Amount} The numerator (top half) of the fraction.
* @param {Amount} denominator The denominator (bottom half) of the fraction.
* @param opts Options for the calculation.
* @param opts.reference_date {Date|Number} Date based on which demurrage/interest
* should be applied. Can be given as JavaScript Date or int for Ripple epoch.
* @return {Amount} The resulting ratio. Unit will be the same as numerator.
*/
Amount.prototype.ratio_human = function(denominator, opts) {
opts = extend({ }, opts);
if (typeof denominator === 'number' && parseInt(denominator, 10) === denominator) {
// Special handling of integer arguments
denominator = Amount.from_json('' + denominator + '.0');
} else {
denominator = Amount.from_json(denominator);
}
var numerator = this;
denominator = Amount.from_json(denominator);
// If either operand is NaN, the result is NaN.
if (!numerator.is_valid() || !denominator.is_valid()) {
return Amount.NaN();
}
// Apply interest/demurrage
//
// We only need to apply it to the second factor, because the currency unit of
// the first factor will carry over into the result.
if (opts.reference_date) {
denominator = denominator.applyInterest(opts.reference_date);
}
// Special case: The denominator is a native (XRP) amount.
//
// In that case, it's going to be expressed as base units (1 XRP =
// 10^xns_precision base units).
//
// However, the unit of the denominator is lost, so when the resulting ratio
// is printed, the ratio is going to be too small by a factor of
// 10^xns_precision.
//
// To compensate, we multiply the numerator by 10^xns_precision.
if (denominator._is_native) {
numerator = numerator.clone();
numerator._value = numerator._value.multiply(consts.bi_xns_unit);
numerator.canonicalize();
}
return numerator.divide(denominator);
};
/**
* Calculate a product of two amounts.
*
* This function allows you to calculate a product between two amounts which
* retains XRPs human/external interpretation (i.e. 1 XRP = 1,000,000 base
* units).
*
* Intended use is to calculate something like: 10 USD * 10 XRP/USD = 100 XRP
*
* @example
* var sell_amount = buy_amount.product_human(price);
*
* @see Amount#ratio_human
*
* @this {Amount} The first factor of the product.
* @param {Amount} factor The second factor of the product.
* @param opts Options for the calculation.
* @param opts.reference_date {Date|Number} Date based on which demurrage/interest
* should be applied. Can be given as JavaScript Date or int for Ripple epoch.
* @return {Amount} The product. Unit will be the same as the first factor.
*/
Amount.prototype.product_human = function(factor, opts) {
opts = opts || {};
if (typeof factor === 'number' && parseInt(factor, 10) === factor) {
// Special handling of integer arguments
factor = Amount.from_json(String(factor) + '.0');
} else {
factor = Amount.from_json(factor);
}
// If either operand is NaN, the result is NaN.
if (!this.is_valid() || !factor.is_valid()) {
return Amount.NaN();
}
// Apply interest/demurrage
//
// We only need to apply it to the second factor, because the currency unit of
// the first factor will carry over into the result.
if (opts.reference_date) {
factor = factor.applyInterest(opts.reference_date);
}
var product = this.multiply(factor);
// Special case: The second factor is a native (XRP) amount expressed as base
// units (1 XRP = 10^xns_precision base units).
//
// See also Amount#ratio_human.
if (factor._is_native) {
product._value = product._value.divide(consts.bi_xns_unit);
product.canonicalize();
}
return product;
};
// True if Amounts are valid and both native or non-native. // True if Amounts are valid and both native or non-native.
Amount.prototype.is_comparable = function(v) { Amount.prototype.is_comparable = function(v) {
return this._value instanceof BigInteger return this._value instanceof BigInteger
@@ -520,50 +624,6 @@ Amount.prototype.issuer = function() {
return this._issuer; return this._issuer;
}; };
// Result in terms of this' currency and issuer.
// XXX Diverges from cpp.
Amount.prototype.multiply = function(v) {
var result;
if (this.is_zero()) {
result = this;
} else if (v.is_zero()) {
result = this.clone();
result._value = BigInteger.ZERO;
} else {
var v1 = this._value;
var o1 = this._offset;
var v2 = v._value;
var o2 = v._offset;
if (this.is_native()) {
while (v1.compareTo(consts.bi_man_min_value) < 0) {
v1 = v1.multiply(consts.bi_10);
o1 -= 1;
}
}
if (v.is_native()) {
while (v2.compareTo(consts.bi_man_min_value) < 0) {
v2 = v2.multiply(consts.bi_10);
o2 -= 1;
}
}
result = new Amount();
result._offset = o1 + o2 + 14;
result._value = v1.multiply(v2).divide(consts.bi_1e14).add(consts.bi_7);
result._is_native = this._is_native;
result._is_negative = this._is_negative !== v._is_negative;
result._currency = this._currency;
result._issuer = this._issuer;
result.canonicalize();
}
return result;
};
// Return a new value. // Return a new value.
Amount.prototype.negate = function() { Amount.prototype.negate = function() {
return this.clone('NEGATE'); return this.clone('NEGATE');
@@ -654,14 +714,14 @@ Amount.prototype.parse_human = function(j, opts) {
fraction += '0'; fraction += '0';
} }
this._is_native = true; this._is_native = true;
this._value = this._value.multiply(consts.bi_xns_unit).add(new BigInteger(fraction)); this._value = this._value.multiply(Amount.bi_xns_unit).add(new BigInteger(fraction));
} else { } else {
// Other currencies have arbitrary precision // Other currencies have arbitrary precision
fraction = fraction.replace(/0+$/, ''); fraction = fraction.replace(/0+$/, '');
precision = fraction.length; precision = fraction.length;
this._is_native = false; this._is_native = false;
var multiplier = consts.bi_10.clone().pow(precision); var multiplier = Amount.bi_10.clone().pow(precision);
this._value = this._value.multiply(multiplier).add(new BigInteger(fraction)); this._value = this._value.multiply(multiplier).add(new BigInteger(fraction));
this._offset = -precision; this._offset = -precision;
@@ -860,8 +920,8 @@ Amount.prototype.parse_native = function(j) {
this._value = new BigInteger(m[2]); this._value = new BigInteger(m[2]);
} else { } else {
// Float notation : values multiplied by 1,000,000. // Float notation : values multiplied by 1,000,000.
var int_part = (new BigInteger(m[2])).multiply(consts.bi_xns_unit); var int_part = (new BigInteger(m[2])).multiply(Amount.bi_xns_unit);
var fraction_part = (new BigInteger(m[3])).multiply(new BigInteger(String(Math.pow(10, 1+consts.xns_precision-m[3].length)))); var fraction_part = (new BigInteger(m[3])).multiply(new BigInteger(String(Math.pow(10, 1+Amount.xns_precision-m[3].length))));
this._value = int_part.add(fraction_part); this._value = int_part.add(fraction_part);
} }
@@ -870,7 +930,7 @@ Amount.prototype.parse_native = function(j) {
this._offset = 0; this._offset = 0;
this._is_negative = !!m[1] && this._value.compareTo(BigInteger.ZERO) !== 0; this._is_negative = !!m[1] && this._value.compareTo(BigInteger.ZERO) !== 0;
if (this._value.compareTo(consts.bi_xns_max) > 0) { if (this._value.compareTo(Amount.bi_xns_max) > 0) {
this._value = NaN; this._value = NaN;
} }
} else { } else {
@@ -912,7 +972,7 @@ Amount.prototype.parse_value = function(j) {
var fraction = new BigInteger(d[3]); var fraction = new BigInteger(d[3]);
var precision = d[3].length; var precision = d[3].length;
this._value = integer.multiply(consts.bi_10.clone().pow(precision)).add(fraction); this._value = integer.multiply(Amount.bi_10.clone().pow(precision)).add(fraction);
this._offset = -precision; this._offset = -precision;
this._is_negative = !!d[1]; this._is_negative = !!d[1];
@@ -953,12 +1013,6 @@ Amount.prototype.set_issuer = function(issuer) {
return this; return this;
}; };
// Result in terms of this' currency and issuer.
Amount.prototype.subtract = function(v) {
// Correctness over speed, less code has less bugs, reuse add code.
return this.add(Amount.from_json(v).negate());
};
Amount.prototype.to_number = function(allow_nan) { Amount.prototype.to_number = function(allow_nan) {
var s = this.to_text(allow_nan); var s = this.to_text(allow_nan);
return typeof s === 'string' ? Number(s) : s; return typeof s === 'string' ? Number(s) : s;
@@ -969,7 +1023,7 @@ Amount.prototype.to_text = function(allow_nan) {
var result = NaN; var result = NaN;
if (this._is_native) { if (this._is_native) {
if (this.is_valid() && this._value.compareTo(consts.bi_xns_max) <= 0){ if (this.is_valid() && this._value.compareTo(Amount.bi_xns_max) <= 0){
result = this._value.toString(); result = this._value.toString();
} }
} else if (this.is_zero()) { } else if (this.is_zero()) {
@@ -1077,8 +1131,8 @@ Amount.prototype.to_human = function(opts) {
ref = this.applyInterest(opts.reference_date); ref = this.applyInterest(opts.reference_date);
} }
var order = ref._is_native ? consts.xns_precision : -ref._offset; var order = ref._is_native ? Amount.xns_precision : -ref._offset;
var denominator = consts.bi_10.clone().pow(order); var denominator = Amount.bi_10.clone().pow(order);
var int_part = ref._value.divide(denominator).toString(); var int_part = ref._value.divide(denominator).toString();
var fraction_part = ref._value.mod(denominator).toString(); var fraction_part = ref._value.mod(denominator).toString();
@@ -1093,17 +1147,27 @@ Amount.prototype.to_human = function(opts) {
if (fraction_part.length || !opts.skip_empty_fraction) { if (fraction_part.length || !opts.skip_empty_fraction) {
// Enforce the maximum number of decimal digits (precision) // Enforce the maximum number of decimal digits (precision)
if (typeof opts.precision === 'number') { if (typeof opts.precision === 'number') {
if (opts.precision === 0 && fraction_part.charCodeAt(0) >= 53) { var precision = Math.max(0, opts.precision);
precision = Math.min(precision, fraction_part.length);
var rounded = Number('0.' + fraction_part).toFixed(precision);
if (rounded < 1) {
fraction_part = rounded.substring(2);
} else {
int_part = (Number(int_part) + 1).toString(); int_part = (Number(int_part) + 1).toString();
fraction_part = '';
}
while (fraction_part.length < precision) {
fraction_part = '0' + fraction_part;
} }
fraction_part = fraction_part.slice(0, opts.precision);
} }
// Limit the number of significant digits (max_sig_digits) // Limit the number of significant digits (max_sig_digits)
if (typeof opts.max_sig_digits === 'number') { if (typeof opts.max_sig_digits === 'number') {
// First, we count the significant digits we have. // First, we count the significant digits we have.
// A zero in the integer part does not count. // A zero in the integer part does not count.
var int_is_zero = +int_part === 0; var int_is_zero = Number(int_part) === 0;
var digits = int_is_zero ? 0 : int_part.length; var digits = int_is_zero ? 0 : int_part.length;
// Don't count leading zeros in the fractional part if the integer part is // Don't count leading zeros in the fractional part if the integer part is
@@ -1129,6 +1193,7 @@ Amount.prototype.to_human = function(opts) {
// Enforce the minimum number of decimal digits (min_precision) // Enforce the minimum number of decimal digits (min_precision)
if (typeof opts.min_precision === 'number') { if (typeof opts.min_precision === 'number') {
opts.min_precision = Math.max(0, opts.min_precision);
while (fraction_part.length < opts.min_precision) { while (fraction_part.length < opts.min_precision) {
fraction_part += '0'; fraction_part += '0';
} }
@@ -1161,7 +1226,7 @@ Amount.prototype.to_human_full = function(opts) {
var a = this.to_human(opts); var a = this.to_human(opts);
var c = this._currency.to_human(); var c = this._currency.to_human();
var i = this._issuer.to_json(opts); var i = this._issuer.to_json(opts);
var o = this.is_native ? (o = a + '/' + c) : (o = a + '/' + c + '/' + i); var o = this.is_native() ? (o = a + '/' + c) : (o = a + '/' + c + '/' + i);
return o; return o;
}; };

View File

@@ -2,6 +2,9 @@
* Data type map. * Data type map.
* *
* Mapping of type ids to data types. The type id is specified by the high * Mapping of type ids to data types. The type id is specified by the high
*
* For reference, see rippled's definition:
* https://github.com/ripple/rippled/blob/develop/src/ripple/data/protocol/SField.cpp
*/ */
var TYPES_MAP = exports.types = [ var TYPES_MAP = exports.types = [
void(0), void(0),
@@ -106,7 +109,8 @@ var FIELDS_MAP = exports.fields = {
16: 'BookDirectory', 16: 'BookDirectory',
17: 'InvoiceID', 17: 'InvoiceID',
18: 'Nickname', 18: 'Nickname',
19: 'Feature' 19: 'Amendment',
20: 'TicketID'
}, },
6: { // Amount 6: { // Amount
1: 'Amount', 1: 'Amount',
@@ -135,7 +139,8 @@ var FIELDS_MAP = exports.fields = {
10: 'ExpireCode', 10: 'ExpireCode',
11: 'CreateCode', 11: 'CreateCode',
12: 'MemoType', 12: 'MemoType',
13: 'MemoData' 13: 'MemoData',
14: 'MemoFormat'
}, },
8: { // Account 8: { // Account
1: 'Account', 1: 'Account',
@@ -187,7 +192,7 @@ var FIELDS_MAP = exports.fields = {
19: { // Vector256 19: { // Vector256
1: 'Indexes', 1: 'Indexes',
2: 'Hashes', 2: 'Hashes',
3: 'Features' 3: 'Amendments'
} }
}; };
@@ -373,7 +378,7 @@ exports.ledger = {
['Balance', REQUIRED], ['Balance', REQUIRED],
['LowLimit', REQUIRED], ['LowLimit', REQUIRED],
['HighLimit', REQUIRED]]) ['HighLimit', REQUIRED]])
} };
exports.metadata = [ exports.metadata = [
[ 'TransactionIndex' , REQUIRED ], [ 'TransactionIndex' , REQUIRED ],
@@ -382,23 +387,32 @@ exports.metadata = [
]; ];
exports.ter = { exports.ter = {
tesSUCCESS: 0, tesSUCCESS : 0,
tecCLAIM: 100, tecCLAIM : 100,
tecPATH_PARTIAL: 101, tecPATH_PARTIAL : 101,
tecUNFUNDED_ADD: 102, tecUNFUNDED_ADD : 102,
tecUNFUNDED_OFFER: 103, tecUNFUNDED_OFFER : 103,
tecUNFUNDED_PAYMENT: 104, tecUNFUNDED_PAYMENT : 104,
tecFAILED_PROCESSING: 105, tecFAILED_PROCESSING : 105,
tecDIR_FULL: 121, tecDIR_FULL : 121,
tecINSUF_RESERVE_LINE: 122, tecINSUF_RESERVE_LINE : 122,
tecINSUF_RESERVE_OFFER: 123, tecINSUF_RESERVE_OFFER : 123,
tecNO_DST: 124, tecNO_DST : 124,
tecNO_DST_INSUF_XRP: 125, tecNO_DST_INSUF_XRP : 125,
tecNO_LINE_INSUF_RESERVE: 126, tecNO_LINE_INSUF_RESERVE : 126,
tecNO_LINE_REDUNDANT: 127, tecNO_LINE_REDUNDANT : 127,
tecPATH_DRY: 128, tecPATH_DRY : 128,
tecUNFUNDED: 129, tecUNFUNDED : 129, // Deprecated, old ambiguous unfunded.
tecMASTER_DISABLED: 130, tecMASTER_DISABLED : 130,
tecNO_REGULAR_KEY: 131, tecNO_REGULAR_KEY : 131,
tecOWNERS: 132 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
}; };

View File

@@ -119,7 +119,9 @@ BlobObj.prototype.init = function(fn) {
self.revision = resp.body.revision; self.revision = resp.body.revision;
self.encrypted_secret = resp.body.encrypted_secret; self.encrypted_secret = resp.body.encrypted_secret;
self.identity_id = resp.body.identity_id;
self.missing_fields = resp.body.missing_fields; self.missing_fields = resp.body.missing_fields;
//self.attestations = resp.body.attestation_summary;
if (!self.decrypt(resp.body.blob)) { if (!self.decrypt(resp.body.blob)) {
return fn(new Error('Error while decrypting blob')); return fn(new Error('Error while decrypting blob'));
@@ -527,17 +529,17 @@ BlobObj.prototype.postUpdate = function(op, pointer, params, fn) {
}; };
/** /**
* get2FA - ECDSA signed request * get2FA - HMAC signed request
*/ */
BlobObj.prototype.get2FA = function (masterkey, fn) { BlobObj.prototype.get2FA = function (fn) {
var config = { var config = {
method : 'GET', method : 'GET',
url : this.url + '/v1/blob/' + this.id + '/2FA?device_id=' + this.device_id, url : this.url + '/v1/blob/' + this.id + '/2FA?device_id=' + this.device_id,
}; };
var signedRequest = new SignedRequest(config); var signedRequest = new SignedRequest(config);
var signed = signedRequest.signAsymmetric(masterkey, this.data.account_id, this.id); var signed = signedRequest.signHmac(this.data.auth_secret, this.id);
request.get(signed.url) request.get(signed.url)
.end(function(err, resp) { .end(function(err, resp) {
@@ -561,7 +563,6 @@ BlobObj.prototype.get2FA = function (masterkey, fn) {
* @params {boolean} options.enabled * @params {boolean} options.enabled
* @params {string} options.phone * @params {string} options.phone
* @params {string} options.country_code * @params {string} options.country_code
* @params {string} options.via //sms, etc
*/ */
BlobObj.prototype.set2FA = function(options, fn) { BlobObj.prototype.set2FA = function(options, fn) {
@@ -572,8 +573,7 @@ BlobObj.prototype.set2FA = function(options, fn) {
data : { data : {
enabled : options.enabled, enabled : options.enabled,
phone : options.phone, phone : options.phone,
country_code : options.country_code, country_code : options.country_code
via : options.via
} }
}; };
@@ -916,14 +916,22 @@ BlobClient.get = function (options, fn) {
* request new token to be sent for 2FA * request new token to be sent for 2FA
* @param {string} url * @param {string} url
* @param {string} id * @param {string} id
* @param {string} force_sms
*/ */
BlobClient.requestToken = function (url, id, fn) { BlobClient.requestToken = function (url, id, force_sms, fn) {
var config = { var config = {
method : 'GET', method : 'GET',
url : url + '/v1/blob/' + id + '/2FA/requestToken' url : url + '/v1/blob/' + id + '/2FA/requestToken'
}; };
if (force_sms && force_sms instanceof Function) {
fn = force_sms;
} else if (force_sms) {
config.url += "?force_sms=true";
}
request.get(config.url) request.get(config.url)
.end(function(err, resp) { .end(function(err, resp) {
if (err) { if (err) {
@@ -1107,48 +1115,6 @@ BlobClient.recoverBlob = function (opts, fn) {
}; };
}; };
/**
* updateProfile
* update information stored outside the blob - HMAC signed
* @param {object}
* @param {string} opts.url
* @param {string} opts.username
* @param {string} opts.auth_secret
* @param {srring} opts.blob_id
* @param {object} opts.profile
* @param {string} opts.profile.phone - optional
* @param {string} opts.profile.country - optional
* @param {string} opts.profile.region - optional
* @param {string} opts.profile.city - optional
*/
BlobClient.updateProfile = function (opts, fn) {
var config = {
method: 'POST',
url: opts.url + '/v1/user/' + opts.username + '/profile',
dataType: 'json',
data: opts.profile
};
var signedRequest = new SignedRequest(config);
var signed = signedRequest.signHmac(opts.auth_secret, opts.blob_id);
request.post(signed.url)
.send(signed.data)
.end(function(err, resp) {
if (err) {
log.error('updateProfile:', err);
fn(new Error('Failed to update profile - XHR error'));
} else if (resp.body && resp.body.result === 'success') {
fn(null, resp.body);
} else if (resp.body) {
log.error('updateProfile:', resp.body);
} else {
fn(new Error('Failed to update profile'));
}
});
};
/** /**
* updateKeys * updateKeys
@@ -1312,6 +1278,7 @@ BlobClient.create = function(options, fn) {
if (err) { if (err) {
fn(err); fn(err);
} else if (resp.body && resp.body.result === 'success') { } else if (resp.body && resp.body.result === 'success') {
blob.identity_id = resp.body.identity_id;
fn(null, blob, resp.body); fn(null, blob, resp.body);
} else if (resp.body && resp.body.result === 'error') { } else if (resp.body && resp.body.result === 'error') {
fn(new Error(resp.body.message)); fn(new Error(resp.body.message));
@@ -1355,4 +1322,263 @@ BlobClient.deleteBlob = function(options, fn) {
}); });
}; };
/*** identity related functions ***/
/**
* updateProfile
* update information stored outside the blob - HMAC signed
* @param {object}
* @param {string} opts.url
* @param {string} opts.auth_secret
* @param {srring} opts.blob_id
* @param {object} opts.profile
* @param {array} opts.profile.attributes (optional, array of attribute objects)
* @param {array} opts.profile.addresses (optional, array of address objects)
*
* @param {string} attribute.id ... id of existing attribute
* @param {string} attribute.name ... attribute name i.e. ripple_address
* @param {string} attribute.type ... optional, sub-type of attribute
* @param {string} attribute.value ... value of attribute
* @param {string} attribute.domain ... corresponding domain
* @param {string} attribute.status ... “current”, “removed”, etc.
* @param {string} attribute.visibitlity ... “public”, ”private”
*/
BlobClient.updateProfile = function (opts, fn) {
var config = {
method: 'POST',
url: opts.url + '/v1/profile/',
dataType: 'json',
data: opts.profile
};
var signedRequest = new SignedRequest(config);
var signed = signedRequest.signHmac(opts.auth_secret, opts.blob_id);
request.post(signed.url)
.send(signed.data)
.end(function(err, resp) {
if (err) {
log.error('updateProfile:', err);
fn(new Error('Failed to update profile - XHR error'));
} else if (resp.body && resp.body.result === 'success') {
fn(null, resp.body);
} else if (resp.body) {
log.error('updateProfile:', resp.body);
fn(new Error('Failed to update profile'));
} else {
fn(new Error('Failed to update profile'));
}
});
};
/**
* getProfile
* @param {Object} opts
* @param {string} opts.url
* @param {string} opts.auth_secret
* @param {srring} opts.blob_id
*/
BlobClient.getProfile = function (opts, fn) {
var config = {
method: 'GET',
url: opts.url + '/v1/profile/'
};
var signedRequest = new SignedRequest(config);
var signed = signedRequest.signHmac(opts.auth_secret, opts.blob_id);
request.get(signed.url)
.send(signed.data)
.end(function(err, resp) {
if (err) {
log.error('getProfile:', err);
fn(new Error('Failed to get profile - XHR error'));
} else if (resp.body && resp.body.result === 'success') {
fn(null, resp.body);
} else if (resp.body) {
log.error('getProfile:', resp.body);
fn(new Error('Failed to get profile'));
} else {
fn(new Error('Failed to get profile'));
}
});
};
/**
* getAttestation
* @param {Object} opts
* @param {string} opts.url
* @param {string} opts.auth_secret
* @param {string} opts.blob_id
* @param {string} opts.type (email,phone,basic_identity)
* @param {object} opts.phone (required for type 'phone')
* @param {string} opts.email (required for type 'email')
*/
BlobClient.getAttestation = function (opts, fn) {
var params = { };
if (opts.phone) params.phone = opts.phone;
if (opts.email) params.email = opts.email;
var config = {
method: 'POST',
url: opts.url + '/v1/attestation/' + opts.type,
dataType: 'json',
data: params
};
var signedRequest = new SignedRequest(config);
var signed = signedRequest.signHmac(opts.auth_secret, opts.blob_id);
request.post(signed.url)
.send(signed.data)
.end(function(err, resp) {
if (err) {
log.error('attest:', err);
fn(new Error('attestation error - XHR error'));
} else if (resp.body && resp.body.result === 'success') {
if (resp.body.attestation) {
resp.body.decoded = BlobClient.parseAttestation(resp.body.attestation);
}
fn(null, resp.body);
} else if (resp.body) {
log.error('attestation:', resp.body);
fn(new Error('attestation error: ' + resp.body.message || ""));
} else {
fn(new Error('attestation error'));
}
});
};
/**
* getAttestationSummary
* @param {Object} opts
* @param {string} opts.url
* @param {string} opts.auth_secret
* @param {string} opts.blob_id
*/
BlobClient.getAttestationSummary = function (opts, fn) {
var config = {
method: 'GET',
url: opts.url + '/v1/attestation/summary',
dataType: 'json'
};
if (opts.full) config.url += '?full=true';
var signedRequest = new SignedRequest(config);
var signed = signedRequest.signHmac(opts.auth_secret, opts.blob_id);
request.get(signed.url)
.send(signed.data)
.end(function(err, resp) {
if (err) {
log.error('attest:', err);
fn(new Error('attestation error - XHR error'));
} else if (resp.body && resp.body.result === 'success') {
if (resp.body.attestation) {
resp.body.decoded = BlobClient.parseAttestation(resp.body.attestation);
}
fn(null, resp.body);
} else if (resp.body) {
log.error('attestation:', resp.body);
fn(new Error('attestation error: ' + resp.body.message || ""));
} else {
fn(new Error('attestation error'));
}
});
};
/**
* updateAttestation
* @param {Object} opts
* @param {string} opts.url
* @param {string} opts.auth_secret
* @param {string} opts.blob_id
* @param {string} opts.type (email,phone,profile,identity)
* @param {object} opts.phone (required for type 'phone')
* @param {object} opts.profile (required for type 'profile')
* @param {string} opts.email (required for type 'email')
* @param {string} opts.answers (required for type 'identity')
* @param {string} opts.token (required for completing email or phone attestations)
*/
BlobClient.updateAttestation = function (opts, fn) {
var params = { };
if (opts.phone) params.phone = opts.phone;
if (opts.profile) params.profile = opts.profile;
if (opts.email) params.email = opts.email;
if (opts.token) params.token = opts.token;
if (opts.answers) params.answers = opts.answers;
var config = {
method: 'POST',
url: opts.url + '/v1/attestation/' + opts.type + '/update',
dataType: 'json',
data: params
};
var signedRequest = new SignedRequest(config);
var signed = signedRequest.signHmac(opts.auth_secret, opts.blob_id);
request.post(signed.url)
.send(signed.data)
.end(function(err, resp) {
if (err) {
log.error('attest:', err);
fn(new Error('attestation error - XHR error'));
} else if (resp.body && resp.body.result === 'success') {
if (resp.body.attestation) {
resp.body.decoded = BlobClient.parseAttestation(resp.body.attestation);
}
fn(null, resp.body);
} else if (resp.body) {
log.error('attestation:', resp.body);
fn(new Error('attestation error: ' + resp.body.message || ""));
} else {
fn(new Error('attestation error'));
}
});
};
/**
* parseAttestation
* @param {Object} attestation
*/
BlobClient.parseAttestation = function (attestation) {
var segments = decodeURIComponent(attestation).split('.');
var decoded;
// base64 decode and parse JSON
try {
decoded = {
header : JSON.parse(crypt.decodeBase64(segments[0])),
payload : JSON.parse(crypt.decodeBase64(segments[1])),
signature : segments[2]
};
} catch (e) {
console.log("invalid attestation:", e);
}
return decoded;
};
exports.BlobClient = BlobClient; exports.BlobClient = BlobClient;

View File

@@ -56,6 +56,18 @@ function keyHash(key, token) {
return sjcl.codec.hex.fromBits(sjcl.bitArray.bitSlice(hmac.encrypt(token), 0, 256)); 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 ******/ /****** exposed functions ******/
/** /**
@@ -213,7 +225,7 @@ Crypt.isValidAddress = function (address) {
*/ */
Crypt.createSecret = function (nWords) { Crypt.createSecret = function (nWords) {
return sjcl.codec.hex.fromBits(sjcl.random.randomWords(nWords)); return sjcl.codec.hex.fromBits(randomWords(nWords));
}; };
/** /**
@@ -221,7 +233,7 @@ Crypt.createSecret = function (nWords) {
*/ */
Crypt.createMaster = function () { Crypt.createMaster = function () {
return base.encode_check(33, sjcl.codec.bytes.fromBits(sjcl.random.randomWords(4))); return base.encode_check(33, sjcl.codec.bytes.fromBits(randomWords(4)));
}; };
@@ -310,4 +322,12 @@ Crypt.base64UrlToBase64 = function(encodedData) {
return encodedData; return encodedData;
}; };
/**
* base64 to UTF8
*/
Crypt.decodeBase64 = function (data) {
return sjcl.codec.utf8String.fromBits(sjcl.codec.base64.toBits(data));
}
exports.Crypt = Crypt; exports.Crypt = Crypt;

View File

@@ -50,14 +50,10 @@ Currency.HEX_CURRENCY_BAD = '0000000000000000000000005852500000000000';
* \s*$ // end with any amount of whitespace * \s*$ // end with any amount of whitespace
* *
*/ */
Currency.prototype.human_RE = /^\s*([a-zA-Z]{3}|[0-9]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/; Currency.prototype.human_RE = /^\s*([a-zA-Z0-9]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/;
Currency.from_json = function(j, shouldInterpretXrpAsIou) { Currency.from_json = function(j, shouldInterpretXrpAsIou) {
if (j instanceof this) { return (new Currency()).parse_json(j, shouldInterpretXrpAsIou);
return j.clone();
} else {
return (new this()).parse_json(j, shouldInterpretXrpAsIou);
}
}; };
Currency.from_human = function(j, opts) { Currency.from_human = function(j, opts) {
@@ -71,12 +67,9 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
switch (typeof j) { switch (typeof j) {
case 'string': case 'string':
if (!j || /^(0|XRP)$/.test(j)) { // if an empty string is given, fall back to XRP
if (shouldInterpretXrpAsIou) { if (!j || j === '0') {
this.parse_hex(Currency.HEX_CURRENCY_BAD); this.parse_hex(shouldInterpretXrpAsIou ? Currency.HEX_CURRENCY_BAD : Currency.HEX_ZERO);
} else {
this.parse_hex(Currency.HEX_ZERO);
}
break; break;
} }
@@ -86,6 +79,17 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
if (matches) { if (matches) {
var currencyCode = matches[1]; 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 // the full currency is matched as it is part of the valid currency format, but not stored
// var full_currency = matches[2] || ''; // var full_currency = matches[2] || '';
var interest = matches[3] || ''; var interest = matches[3] || '';
@@ -270,7 +274,7 @@ Currency.prototype.has_interest = function() {
* @returns {number} - interest for provided interval, can be negative for demurred currencies * @returns {number} - interest for provided interval, can be negative for demurred currencies
*/ */
Currency.prototype.get_interest_at = function(referenceDate, decimals) { Currency.prototype.get_interest_at = function(referenceDate, decimals) {
if (!this.has_interest) { if (!this.has_interest()) {
return 0; return 0;
} }
@@ -316,17 +320,17 @@ Currency.prototype.to_json = function(opts) {
var opts = opts || {}; var opts = opts || {};
var currency; var currency;
var fullName = opts && opts.full_name ? " - " + opts.full_name : ""; var fullName = opts && opts.full_name ? ' - ' + opts.full_name : '';
opts.show_interest = opts.show_interest !== void(0) ? opts.show_interest : this.has_interest();
// Any currency with standard properties and a valid code can be abbreviated if (!opts.force_hex && /^[A-Z0-9]{3}$/.test(this._iso_code)) {
// in the JSON wire format as the three character code.
if (!opts.force_hex && /^[A-Z0-9]{3}$/.test(this._iso_code) && !this.has_interest()) {
currency = this._iso_code + fullName; 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)';
}
// If there is interest, append the annual interest to the full currency name
} else if (!opts.force_hex && this.has_interest()) {
var decimals = opts ? opts.decimals : undefined;
currency = this._iso_code + fullName + " (" + this.get_interest_percentage_at(this._interest_start + 3600 * 24 * 365, decimals) + "%pa)";
} else { } else {
// Fallback to returning the raw currency hex // Fallback to returning the raw currency hex

View File

@@ -18,6 +18,7 @@ exports.RippleTxt = require('./rippletxt').RippleTxt;
exports.binformat = require('./binformat'); exports.binformat = require('./binformat');
exports.utils = require('./utils'); exports.utils = require('./utils');
exports.Server = require('./server').Server; exports.Server = require('./server').Server;
exports.Wallet = require('./wallet');
// Important: We do not guarantee any specific version of SJCL or for any // 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 // specific features to be included. The version and configuration may change at

View File

@@ -5,7 +5,7 @@ var UInt256 = require('./uint256').UInt256;
var Base = require('./base').Base; var Base = require('./base').Base;
function KeyPair() { function KeyPair() {
this._curve = sjcl.ecc.curves.c256; this._curve = sjcl.ecc.curves.k256;
this._secret = null; this._secret = null;
this._pubkey = null; this._pubkey = null;
}; };
@@ -15,7 +15,7 @@ KeyPair.from_bn_secret = function(j) {
}; };
KeyPair.prototype.parse_bn_secret = function(j) { KeyPair.prototype.parse_bn_secret = function(j) {
this._secret = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves.c256, j); this._secret = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves.k256, j);
return this; return this;
}; };

View File

@@ -5,47 +5,51 @@ var Amount = require('./amount').Amount;
/** /**
* Meta data processing facility * Meta data processing facility
*
* @constructor
* @param {Object} transaction metadata
*/ */
function Meta(raw_data) { function Meta(data) {
var self = this; var self = this;
this.nodes = [ ]; this.nodes = [ ];
raw_data.AffectedNodes.forEach(function(an) { if (typeof data !== 'object') {
var result = { }; throw new TypeError('Missing metadata');
}
if ((result.diffType = self.diffType(an))) { if (!Array.isArray(data.AffectedNodes)) {
an = an[result.diffType]; throw new TypeError('Metadata missing AffectedNodes');
}
result.entryType = an.LedgerEntryType; data.AffectedNodes.forEach(this.addNode, this);
result.ledgerIndex = an.LedgerIndex;
result.fields = extend({}, an.PreviousFields, an.NewFields, an.FinalFields);
result.fieldsPrev = an.PreviousFields || {};
result.fieldsNew = an.NewFields || {};
result.fieldsFinal = an.FinalFields || {};
// getAffectedBooks will set this
// result.bookKey = undefined;
self.nodes.push(result);
}
});
}; };
Meta.node_types = [ Meta.nodeTypes = [
'CreatedNode', 'CreatedNode',
'ModifiedNode', 'ModifiedNode',
'DeletedNode' 'DeletedNode'
]; ];
Meta.prototype.diffType = function(an) { Meta.amountFieldsAffectingIssuer = [
var result = false; 'LowLimit',
'HighLimit',
'TakerPays',
'TakerGets'
];
for (var i=0; i<Meta.node_types.length; i++) { /**
var x = Meta.node_types[i]; * @api private
if (an.hasOwnProperty(x)) { */
result = x;
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; break;
} }
} }
@@ -53,6 +57,63 @@ Meta.prototype.diffType = function(an) {
return result; 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. * Execute a function on each affected node.
* *
@@ -61,7 +122,7 @@ Meta.prototype.diffType = function(an) {
* *
* { * {
* // Type of diff, e.g. CreatedNode, ModifiedNode * // Type of diff, e.g. CreatedNode, ModifiedNode
* diffType: 'CreatedNode' * nodeType: 'CreatedNode'
* *
* // Type of node affected, e.g. RippleState, AccountRoot * // Type of node affected, e.g. RippleState, AccountRoot
* entryType: 'RippleState', * entryType: 'RippleState',
@@ -72,7 +133,7 @@ Meta.prototype.diffType = function(an) {
* // Contains all fields with later versions taking precedence * // Contains all fields with later versions taking precedence
* // * //
* // This is a shorthand for doing things like checking which account * // 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: {...}, * fields: {...},
* *
* // Old fields (before the change) * // Old fields (before the change)
@@ -88,43 +149,39 @@ Meta.prototype.diffType = function(an) {
* The second parameter to the callback is the index of the node in the metadata * The second parameter to the callback is the index of the node in the metadata
* (first entry is index 0). * (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);
}
};
([ [
'forEach', 'forEach',
'map', 'map',
'filter', 'filter',
'every', 'every',
'some',
'reduce' 'reduce'
]).forEach(function(fn) { ].forEach(function(fn) {
Meta.prototype[fn] = function() { Meta.prototype[fn] = function() {
return Array.prototype[fn].apply(this.nodes, arguments); return Array.prototype[fn].apply(this.nodes, arguments);
}; };
}); });
var amountFieldsAffectingIssuer = [ Meta.prototype.each = Meta.prototype.forEach;
'LowLimit',
'HighLimit', Meta.prototype.getAffectedAccounts = function(from) {
'TakerPays', if (this._affectedAccounts) {
'TakerGets' return this._affectedAccounts;
]; }
Meta.prototype.getAffectedAccounts = function() {
var accounts = [ ]; var accounts = [ ];
// This code should match the behavior of the C++ method: // This code should match the behavior of the C++ method:
// TransactionMetaSet::getAffectedAccounts // TransactionMetaSet::getAffectedAccounts
this.nodes.forEach(function(an) { for (var i=0; i<this.nodes.length; i++) {
var fields = (an.diffType === 'CreatedNode') ? an.fieldsNew : an.fieldsFinal; var node = this.nodes[i];
for (var i in fields) { var fields = (node.nodeType === 'CreatedNode') ? node.fieldsNew : node.fieldsFinal;
var field = fields[i]; for (var fieldName in fields) {
var field = fields[fieldName];
if (typeof field === 'string' && UInt160.is_valid(field)) { if (typeof field === 'string' && UInt160.is_valid(field)) {
accounts.push(field); accounts.push(field);
} else if (amountFieldsAffectingIssuer.indexOf(i) !== -1) { } else if (~Meta.amountFieldsAffectingIssuer.indexOf(fieldName)) {
var amount = Amount.from_json(field); var amount = Amount.from_json(field);
var issuer = amount.issuer(); var issuer = amount.issuer();
if (issuer.is_valid() && !issuer.is_zero()) { if (issuer.is_valid() && !issuer.is_zero()) {
@@ -132,43 +189,53 @@ Meta.prototype.getAffectedAccounts = function() {
} }
} }
} }
}); }
return utils.arrayUnique(accounts); this._affectedAccounts = utils.arrayUnique(accounts);
return this._affectedAccounts;
}; };
Meta.prototype.getAffectedBooks = function() { Meta.prototype.getAffectedBooks = function() {
if (this._affectedBooks) {
return this._affectedBooks;
}
var books = [ ]; var books = [ ];
this.nodes.forEach(function(an) { for (var i=0; i<this.nodes.length; i++) {
if (an.entryType !== 'Offer') { var node = this.nodes[i];
return;
if (node.entryType !== 'Offer') {
continue;
} }
var gets = Amount.from_json(an.fields.TakerGets); var gets = Amount.from_json(node.fields.TakerGets);
var pays = Amount.from_json(an.fields.TakerPays); var pays = Amount.from_json(node.fields.TakerPays);
var getsKey = gets.currency().to_json(); var getsKey = gets.currency().to_json();
var paysKey = pays.currency().to_json();
if (getsKey !== 'XRP') { if (getsKey !== 'XRP') {
getsKey += '/' + gets.issuer().to_json(); getsKey += '/' + gets.issuer().to_json();
} }
var paysKey = pays.currency().to_json();
if (paysKey !== 'XRP') { if (paysKey !== 'XRP') {
paysKey += '/' + pays.issuer().to_json(); paysKey += '/' + pays.issuer().to_json();
} }
var key = [ getsKey, paysKey ].join(':'); var key = getsKey + ':' + paysKey;
// Hell of a lot of work, so we are going to cache this. We can use this // 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 // later to good effect in OrderBook.notify to make sure we only process
// pertinent offers. // pertinent offers.
an.bookKey = key; node.bookKey = key;
books.push(key); books.push(key);
}); }
return utils.arrayUnique(books); this._affectedBooks = utils.arrayUnique(books);
return this._affectedBooks;
}; };
exports.Meta = Meta; exports.Meta = Meta;

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
var util = require('util'); var util = require('util');
var assert = require('assert');
var LRU = require('lru-cache'); var LRU = require('lru-cache');
var Server = require('./server').Server; var Server = require('./server').Server;
var Request = require('./request').Request; var Request = require('./request').Request;
@@ -22,6 +23,7 @@ var Server = require('./server').Server;
var Amount = require('./amount').Amount; var Amount = require('./amount').Amount;
var Currency = require('./currency').Currency; var Currency = require('./currency').Currency;
var UInt160 = require('./uint160').UInt160; var UInt160 = require('./uint160').UInt160;
var UInt256 = require('./uint256').UInt256;
var Transaction = require('./transaction').Transaction; var Transaction = require('./transaction').Transaction;
var Account = require('./account').Account; var Account = require('./account').Account;
var Meta = require('./meta').Meta; var Meta = require('./meta').Meta;
@@ -83,6 +85,7 @@ function Remote(opts, trace) {
this.trusted = Boolean(opts.trusted); this.trusted = Boolean(opts.trusted);
this.state = 'offline'; // 'online', 'offline' this.state = 'offline'; // 'online', 'offline'
this._server_fatal = false; // True, if we know server exited. this._server_fatal = false; // True, if we know server exited.
this._allow_partial_history = (typeof opts.allow_partial_history === 'boolean') ? opts.allow_partial_history : true;
this.local_sequence = Boolean(opts.local_sequence); // Locally track sequence numbers this.local_sequence = Boolean(opts.local_sequence); // Locally track sequence numbers
this.local_fee = (typeof opts.local_fee === 'boolean') ? opts.local_fee : true;// Locally set fees this.local_fee = (typeof opts.local_fee === 'boolean') ? opts.local_fee : true;// Locally set fees
@@ -90,7 +93,9 @@ function Remote(opts, trace) {
this.canonical_signing = (typeof opts.canonical_signing === 'boolean') ? opts.canonical_signing : true; this.canonical_signing = (typeof opts.canonical_signing === 'boolean') ? opts.canonical_signing : true;
this.fee_cushion = (typeof opts.fee_cushion === 'number') ? opts.fee_cushion : 1.2; this.fee_cushion = (typeof opts.fee_cushion === 'number') ? opts.fee_cushion : 1.2;
this.max_fee = (typeof opts.max_fee === 'number') ? opts.max_fee : Infinity; this.max_fee = (typeof opts.max_fee === 'number') ? opts.max_fee : 1000000; // default max fee is 1 XRP, 10^6 drops
this.max_attempts = (typeof opts.max_attempts === 'number') ? opts.max_attempts : 10;
this._ledger_current_index = void(0); this._ledger_current_index = void(0);
this._ledger_hash = void(0); this._ledger_hash = void(0);
@@ -106,6 +111,7 @@ function Remote(opts, trace) {
this._should_connect = true; this._should_connect = true;
this._submission_timeout = 1000 * (typeof opts.submission_timeout === 'number' ? opts.submission_timeout : 20); this._submission_timeout = 1000 * (typeof opts.submission_timeout === 'number' ? opts.submission_timeout : 20);
this._last_ledger_offset = (typeof opts.last_ledger_offset === 'number') ? opts.last_ledger_offset : 3;
this._received_tx = LRU({ max: 100 }); this._received_tx = LRU({ max: 100 });
this._cur_path_find = null; this._cur_path_find = null;
@@ -208,7 +214,7 @@ function Remote(opts, trace) {
function listenerAdded(type, listener) { function listenerAdded(type, listener) {
if (type === 'transaction_all') { if (type === 'transaction_all') {
if (!self._transaction_subs && self._connected) { if (!self._transaction_subs && self.isConnected()) {
self.request_subscribe('transactions').request(); self.request_subscribe('transactions').request();
} }
self._transaction_subs += 1; self._transaction_subs += 1;
@@ -220,7 +226,7 @@ function Remote(opts, trace) {
function listenerRemoved(type, listener) { function listenerRemoved(type, listener) {
if (type === 'transaction_all') { if (type === 'transaction_all') {
self._transaction_subs -= 1; self._transaction_subs -= 1;
if (!self._transaction_subs && self._connected) { if (!self._transaction_subs && self.isConnected()) {
self.request_unsubscribe('transactions').request(); self.request_unsubscribe('transactions').request();
} }
} }
@@ -242,7 +248,7 @@ function Remote(opts, trace) {
}; };
if (opts.ping) { if (opts.ping) {
this.once('connect', pingServers); this.on('connect', pingServers);
} }
function reconnect() { function reconnect() {
@@ -337,13 +343,11 @@ Remote.isValidLedgerData = function(message) {
return (typeof message === 'object') return (typeof message === 'object')
&& (typeof message.fee_base === 'number') && (typeof message.fee_base === 'number')
&& (typeof message.fee_ref === 'number') && (typeof message.fee_ref === 'number')
&& (typeof message.fee_base === 'number')
&& (typeof message.ledger_hash === 'string') && (typeof message.ledger_hash === 'string')
&& (typeof message.ledger_index === 'number') && (typeof message.ledger_index === 'number')
&& (typeof message.ledger_time === 'number') && (typeof message.ledger_time === 'number')
&& (typeof message.reserve_base === 'number') && (typeof message.reserve_base === 'number')
&& (typeof message.reserve_inc === 'number') && (typeof message.reserve_inc === 'number');
&& (typeof message.txn_count === 'number');
}; };
/** /**
@@ -513,12 +517,9 @@ Remote.prototype.reconnect = function() {
log.info('reconnecting'); log.info('reconnecting');
;(function nextServer(i) { this._servers.forEach(function(server) {
self._servers[i].reconnect(); server.reconnect();
if (++i < self._servers.length) { });
nextServer(i);
}
})(0);
return this; return this;
}; };
@@ -552,12 +553,9 @@ Remote.prototype.connect = function(online) {
this._should_connect = true; this._should_connect = true;
;(function nextServer(i) { this._servers.forEach(function(server) {
self._servers[i].connect(); server.connect();
if (++i < self._servers.length) { });
nextServer(i);
}
})(0);
return this; return this;
}; };
@@ -576,7 +574,7 @@ Remote.prototype.disconnect = function(callback) {
var callback = (typeof callback === 'function') ? callback : function(){}; var callback = (typeof callback === 'function') ? callback : function(){};
if (!this._connected) { if (!this.isConnected()) {
callback(); callback();
return this; return this;
} }
@@ -621,16 +619,16 @@ Remote.prototype._handleMessage = function(message, server) {
switch (message.type) { switch (message.type) {
case 'ledgerClosed': case 'ledgerClosed':
this._handleLedgerClosed(message); this._handleLedgerClosed(message, server);
break; break;
case 'serverStatus': case 'serverStatus':
this._handleServerStatus(message); this._handleServerStatus(message, server);
break; break;
case 'transaction': case 'transaction':
this._handleTransaction(message); this._handleTransaction(message, server);
break; break;
case 'path_find': case 'path_find':
this._handlePathFind(message); this._handlePathFind(message, server);
break; break;
default: default:
if (this.trace) { if (this.trace) {
@@ -646,7 +644,7 @@ Remote.prototype._handleMessage = function(message, server) {
* @param {Object} message * @param {Object} message
*/ */
Remote.prototype._handleLedgerClosed = function(message) { Remote.prototype._handleLedgerClosed = function(message, server) {
var self = this; var self = this;
// XXX If not trusted, need to verify we consider ledger closed. // XXX If not trusted, need to verify we consider ledger closed.
@@ -659,11 +657,19 @@ Remote.prototype._handleLedgerClosed = function(message) {
var ledgerAdvanced = message.ledger_index >= this._ledger_current_index; var ledgerAdvanced = message.ledger_index >= this._ledger_current_index;
if (ledgerAdvanced) { if (isNaN(this._ledger_current_index) || ledgerAdvanced) {
this._ledger_time = message.ledger_time; this._ledger_time = message.ledger_time;
this._ledger_hash = message.ledger_hash; this._ledger_hash = message.ledger_hash;
this._ledger_current_index = message.ledger_index + 1; this._ledger_current_index = message.ledger_index + 1;
this.emit('ledger_closed', message);
if (this.isConnected()) {
this.emit('ledger_closed', message, server);
} else {
this.once('connect', function() {
// Delay until server is 'online'
self.emit('ledger_closed', message, server);
});
}
} }
}; };
@@ -673,8 +679,8 @@ Remote.prototype._handleLedgerClosed = function(message) {
* @param {Object} message * @param {Object} message
*/ */
Remote.prototype._handleServerStatus = function(message) { Remote.prototype._handleServerStatus = function(message, server) {
this.emit('server_status', message); this.emit('server_status', message, server);
}; };
/** /**
@@ -683,7 +689,7 @@ Remote.prototype._handleServerStatus = function(message) {
* @param {Object} message * @param {Object} message
*/ */
Remote.prototype._handleTransaction = function(message) { Remote.prototype._handleTransaction = function(message, server) {
var self = this; var self = this;
// XXX If not trusted, need proof. // XXX If not trusted, need proof.
@@ -730,8 +736,8 @@ Remote.prototype._handleTransaction = function(message) {
}); });
} }
this.emit('transaction', message); this.emit('transaction', message, server);
this.emit('transaction_all', message); this.emit('transaction_all', message, server);
}; };
/** /**
@@ -740,7 +746,7 @@ Remote.prototype._handleTransaction = function(message) {
* @param {Object} message * @param {Object} message
*/ */
Remote.prototype._handlePathFind = function(message) { Remote.prototype._handlePathFind = function(message, server) {
var self = this; var self = this;
// Pass the event to the currently open PathFind object // Pass the event to the currently open PathFind object
@@ -748,7 +754,7 @@ Remote.prototype._handlePathFind = function(message) {
this._cur_path_find.notify_update(message); this._cur_path_find.notify_update(message);
} }
this.emit('path_find_all', message); this.emit('path_find_all', message, server);
}; };
/** /**
@@ -788,6 +794,20 @@ Remote.prototype.isConnected = function() {
return this._connected; return this._connected;
}; };
/**
* Get array of connected servers
*/
Remote.prototype.getConnectedServers = function() {
var servers = [ ];
for (var i=0; i<this._servers.length; i++) {
if (this._servers[i].isConnected()) {
servers.push(this._servers[i]);
}
}
return servers;
};
/** /**
* Select a server to handle a request. Servers are * Select a server to handle a request. Servers are
* automatically prioritized * automatically prioritized
@@ -795,41 +815,32 @@ Remote.prototype.isConnected = function() {
Remote.prototype._getServer = Remote.prototype._getServer =
Remote.prototype.getServer = function() { Remote.prototype.getServer = function() {
var result = void(0);
if (this._primary_server && this._primary_server.isConnected()) { if (this._primary_server && this._primary_server.isConnected()) {
return this._primary_server; return this._primary_server;
} }
if (!this._servers.length) { if (!this._servers.length) {
return result; return null;
} }
function sortByScore(a, b) { var connectedServers = this.getConnectedServers();
var aScore = a._score + a._fee; if (connectedServers.length === 0 || !connectedServers[0]) {
var bScore = b._score + b._fee; return null;
if (aScore > bScore) { }
return 1;
} else if (aScore < bScore) {
return -1;
} else {
return 0;
}
};
// Sort servers by score var server = connectedServers[0];
this._servers.sort(sortByScore); var cScore = server._score + server._fee;
// First connected server for (var i=1; i<connectedServers.length; i++) {
for (var i=0; i<this._servers.length; i++) { var _server = connectedServers[i];
var server = this._servers[i]; var bScore = _server._score + _server._fee;
if ((server instanceof Server) && server._connected) { if (bScore < cScore) {
result = server; server = _server;
break; cScore = bScore;
} }
} }
return result; return server;
}; };
/** /**
@@ -859,7 +870,7 @@ Remote.prototype.request = function(request) {
if (!this._servers.length) { if (!this._servers.length) {
request.emit('error', new Error('No servers available')); request.emit('error', new Error('No servers available'));
} else if (!this._connected) { } else if (!this.isConnected()) {
this.once('connect', this.request.bind(this, request)); this.once('connect', this.request.bind(this, request));
} else if (request.server === null) { } else if (request.server === null) {
request.emit('error', new Error('Server does not exist')); request.emit('error', new Error('Server does not exist'));
@@ -922,21 +933,22 @@ Remote.prototype.requestServerInfo = function(callback) {
* @return {Request} request * @return {Request} request
*/ */
Remote.prototype.requestLedger = function(ledger, options, callback) { Remote.prototype.requestLedger = function(options, callback) {
// XXX This is a bad command. Some variants don't scale. // XXX This is a bad command. Some variants don't scale.
// XXX Require the server to be trusted. // XXX Require the server to be trusted.
//utils.assert(this.trusted); //utils.assert(this.trusted);
var request = new Request(this, 'ledger'); var request = new Request(this, 'ledger');
if (ledger) {
// DEPRECATED: use .ledger_hash() or .ledger_index()
//console.log('request_ledger: ledger parameter is deprecated');
request.message.ledger = ledger;
}
switch (typeof options) { switch (typeof options) {
case 'undefined': break;
case 'function':
callback = options;
break;
case 'object': case 'object':
if (!options) break;
Object.keys(options).forEach(function(o) { Object.keys(options).forEach(function(o) {
switch (o) { switch (o) {
case 'full': case 'full':
@@ -945,21 +957,21 @@ Remote.prototype.requestLedger = function(ledger, options, callback) {
case 'accounts': case 'accounts':
request.message[o] = true; request.message[o] = true;
break; break;
case 'ledger_index':
case 'ledger_hash':
request.message[o] = options[o];
break;
case 'closed' :
case 'current' :
case 'validated' :
request.message.ledger_index = o;
break;
} }
}, options); }, options);
break; break;
case 'function':
callback = options;
options = void(0);
break;
default: default:
//DEPRECATED request.ledgerSelect(options);
if (this.trace) {
log.info('request_ledger: full parameter is deprecated');
}
request.message.full = true;
break; break;
} }
@@ -1188,19 +1200,43 @@ Remote.prototype.requestTx = function(hash, callback) {
}; };
/** /**
* Account request abstraction * Account Request
* *
* @api private * Optional paging with limit and marker options
* supported in rippled for 'account_lines' and 'account_offers'
*
* The paged responses aren't guaranteed to be reliable between
* ledger closes. You have to supply a ledger_index or ledger_hash
* when paging to ensure a complete response
*
* @param {String} type - request name, e.g. 'account_lines'
* @param {String} account - ripple address
* @param {Object} options - all optional
* @param {String} peer - ripple address
* @param [String|Number] ledger identifier
* @param [Number] limit - max results per response
* @param {String} marker - start position in response paging
* @param [Function] callback
* @return {Request}
* @throws {Error} if a marker is provided, but no ledger_index or ledger_hash
*/ */
Remote.accountRequest = function(type, options, callback) {
var account, ledger, peer, limit, marker;
Remote.accountRequest = function(type, account, accountIndex, ledger, peer, callback) { if (typeof options === 'object') {
if (typeof account === 'object') { account = options.account;
var options = account; ledger = options.ledger;
callback = accountIndex; peer = options.peer;
ledger = options.ledger; limit = options.limit;
accountIndex = options.account_index || options.accountIndex; marker = options.marker;
account = options.accountID || options.account; }
peer = options.peer;
// if a marker is given, we need a ledger
// check if a valid ledger_index or ledger_hash is provided
if (marker) {
if (!(Number(ledger) > 0) && !UInt256.is_valid(ledger)) {
throw new Error('A ledger_index or ledger_hash must be provided when using a marker');
}
} }
var lastArg = arguments[arguments.length - 1]; var lastArg = arguments[arguments.length - 1];
@@ -1210,37 +1246,55 @@ Remote.accountRequest = function(type, account, accountIndex, ledger, peer, call
} }
var request = new Request(this, type); var request = new Request(this, type);
var account = UInt160.json_rewrite(account);
request.message.ident = account; //DEPRECATED; if (account) {
request.message.account = account; account = UInt160.json_rewrite(account);
request.message.account = account;
if (typeof accountIndex === 'number') {
request.message.index = accountIndex;
} }
if (!/^(undefined|function)$/.test(typeof ledger)) { request.ledgerSelect(ledger);
request.ledgerChoose(ledger);
}
if (!/^(undefined|function)$/.test(typeof peer)) { if (UInt160.is_valid(peer)) {
request.message.peer = UInt160.json_rewrite(peer); request.message.peer = UInt160.json_rewrite(peer);
} }
request.callback(callback); if (!isNaN(Number(limit))) {
limit = Number(limit);
// max for 32-bit unsigned int is 4294967295
// we'll clamp to 1e9
if (limit > 1e9) {
limit = 1e9;
}
// min for 32-bit unsigned int is 0
// we'll clamp to 0
if (limit < 0) {
limit = 0;
}
request.message.limit = limit;
}
if (marker) {
request.message.marker = marker;
}
request.callback(callback);
return request; return request;
}; };
/** /**
* Request account_info * Request account_info
* *
* @param {String} ripple address * @param {Object} options
* @param {String} account - ripple address
* @param {String} peer - ripple address
* @param [String|Number] ledger identifier
* @param [Function] callback * @param [Function] callback
* @return {Request} * @return {Request}
*/ */
Remote.prototype.requestAccountInfo = function(account, callback) { Remote.prototype.requestAccountInfo = function(options, callback) {
var args = Array.prototype.concat.apply(['account_info'], arguments); var args = Array.prototype.concat.apply(['account_info'], arguments);
return Remote.accountRequest.apply(this, args); return Remote.accountRequest.apply(this, args);
}; };
@@ -1248,12 +1302,15 @@ Remote.prototype.requestAccountInfo = function(account, callback) {
/** /**
* Request account_currencies * Request account_currencies
* *
* @param {String} ripple address * @param {Object} options
* @param {String} account - ripple address
* @param {String} peer - ripple address
* @param [String|Number] ledger identifier
* @param [Function] callback * @param [Function] callback
* @return {Request} * @return {Request}
*/ */
Remote.prototype.requestAccountCurrencies = function(account, callback) { Remote.prototype.requestAccountCurrencies = function(options, callback) {
var args = Array.prototype.concat.apply(['account_currencies'], arguments); var args = Array.prototype.concat.apply(['account_currencies'], arguments);
return Remote.accountRequest.apply(this, args); return Remote.accountRequest.apply(this, args);
}; };
@@ -1261,15 +1318,24 @@ Remote.prototype.requestAccountCurrencies = function(account, callback) {
/** /**
* Request account_lines * Request account_lines
* *
* @param {String} ripple address * Requests for account_lines support paging, provide a limit and marker
* @param {Number] sub-account index * to page through responses.
* @param [String|Number] ledger *
* @param [String] peer * The paged responses aren't guaranteed to be reliable between
* ledger closes. You have to supply a ledger_index or ledger_hash
* when paging to ensure a complete response
*
* @param {Object} options
* @param {String} account - ripple address
* @param {String} peer - ripple address
* @param [String|Number] ledger identifier
* @param [Number] limit - max results per response
* @param {String} marker - start position in response paging
* @param [Function] callback * @param [Function] callback
* @return {Request} * @return {Request}
*/ */
Remote.prototype.requestAccountLines = function(account, accountIndex, ledger, peer, callback) { Remote.prototype.requestAccountLines = function(options, callback) {
// XXX Does this require the server to be trusted? // XXX Does this require the server to be trusted?
//utils.assert(this.trusted); //utils.assert(this.trusted);
var args = Array.prototype.concat.apply(['account_lines'], arguments); var args = Array.prototype.concat.apply(['account_lines'], arguments);
@@ -1279,20 +1345,27 @@ Remote.prototype.requestAccountLines = function(account, accountIndex, ledger, p
/** /**
* Request account_offers * Request account_offers
* *
* @param {String} ripple address * Requests for account_offers support paging, provide a limit and marker
* @param {Number] sub-account index * to page through responses.
* @param [String|Number] ledger *
* @param [String] peer * The paged responses aren't guaranteed to be reliable between
* ledger closes. You have to supply a ledger_index or ledger_hash
* when paging to ensure a complete response
*
* @param {Object} options
* @param {String} account - ripple address
* @param [String|Number] ledger identifier
* @param [Number] limit - max results per response
* @param {String} marker - start position in response paging
* @param [Function] callback * @param [Function] callback
* @return {Request} * @return {Request}
*/ */
Remote.prototype.requestAccountOffers = function(account, accountIndex, ledger, callback) { Remote.prototype.requestAccountOffers = function(options, callback) {
var args = Array.prototype.concat.apply(['account_offers'], arguments); var args = Array.prototype.concat.apply(['account_offers'], arguments);
return Remote.accountRequest.apply(this, args); return Remote.accountRequest.apply(this, args);
}; };
/** /**
* Request account_tx * Request account_tx
* *
@@ -1308,9 +1381,6 @@ Remote.prototype.requestAccountOffers = function(account, accountIndex, ledger,
* @param [Number] offset, defaults to 0 * @param [Number] offset, defaults to 0
* @param [Number] limit * @param [Number] limit
* *
* @param [Function] filter
* @param [Function] map
* @param [Function] reduce
* @param [Function] callback * @param [Function] callback
* @return {Request} * @return {Request}
*/ */
@@ -1330,6 +1400,10 @@ Remote.prototype.requestAccountTx = function(options, callback) {
options.ledger_index_max = options.max_ledger; options.ledger_index_max = options.max_ledger;
} }
if (options.binary && options.parseBinary === void(0)) {
options.parseBinary = true;
}
Object.keys(options).forEach(function(o) { Object.keys(options).forEach(function(o) {
switch (o) { switch (o) {
case 'account': case 'account':
@@ -1349,101 +1423,32 @@ Remote.prototype.requestAccountTx = function(options, callback) {
} }
}, options); }, options);
function propertiesFilter(obj, transaction) { request.once('success', function(res) {
var properties = Object.keys(obj); if (options.parseBinary) {
return function(transaction) { res.transactions = res.transactions.map(Remote.parseBinaryTransaction);
var result = properties.every(function(property) {
return transaction.tx[property] === obj[property];
});
return result;
};
};
function parseBinaryTransaction(transaction) {
var tx = { validated: transaction.validated };
tx.meta = new SerializedObject(transaction.meta).to_json();
tx.tx = new SerializedObject(transaction.tx_blob).to_json();
tx.tx.ledger_index = transaction.ledger_index;
tx.tx.hash = Transaction.from_json(tx.tx).hash();
return tx;
};
function accountTxFilter(fn) {
if (typeof fn !== 'function') {
throw new Error('Missing filter function');
} }
request.emit('transactions', res);
});
var self = this; request.callback(callback, 'transactions');
function filterHandler() {
var listeners = self.listeners('success');
self.removeAllListeners('success');
self.once('success', function(res) {
if (options.parseBinary) {
res.transactions = res.transactions.map(parseBinaryTransaction);
}
if (fn !== Boolean) {
res.transactions = res.transactions.filter(fn);
}
if (typeof options.map === 'function') {
res.transactions = res.transactions.map(options.map);
}
if (typeof options.reduce === 'function') {
res.transactions = res.transactions.reduce(options.reduce);
}
if (typeof options.pluck === 'string') {
res = res[options.pluck];
}
listeners.forEach(function(listener) {
listener.call(self, res);
});
});
};
this.once('request', filterHandler);
return this;
};
request.filter = accountTxFilter;
if (typeof options.parseBinary !== 'boolean') {
options.parseBinary = true;
}
if (options.binary || (options.map || options.reduce)) {
options.filter = options.filter || Boolean;
}
if (options.filter) {
switch (options.filter) {
case 'inbound':
request.filter(propertiesFilter({ Destination: options.account }));
break;
case 'outbound':
request.filter(propertiesFilter({ Account: options.account }));
break;
default:
if (typeof options.filter === 'object') {
options.filter = propertiesFilter(options.filter);
}
request.filter(options.filter);
}
}
request.callback(callback);
return request; return request;
}; };
/**
* @param {Object} transaction
* @return {Transaction}
*/
Remote.parseBinaryTransaction = function(transaction) {
var tx = { validated: transaction.validated };
tx.meta = new SerializedObject(transaction.meta).to_json();
tx.tx = new SerializedObject(transaction.tx_blob).to_json();
tx.tx.ledger_index = transaction.ledger_index;
tx.tx.hash = Transaction.from_json(tx.tx).hash();
return tx;
};
/** /**
* Request the overall transaction history. * Request the overall transaction history.
* *
@@ -1479,19 +1484,19 @@ Remote.prototype.requestTxHistory = function(start, callback) {
*/ */
Remote.prototype.requestBookOffers = function(gets, pays, taker, callback) { Remote.prototype.requestBookOffers = function(gets, pays, taker, callback) {
if (gets.hasOwnProperty('pays')) { var lastArg = arguments[arguments.length - 1];
if (gets.hasOwnProperty('gets') || gets.hasOwnProperty('taker_gets')) {
var options = gets; var options = gets;
// This would mutate the `lastArg` in `arguments` to be `null` and is // This would mutate the `lastArg` in `arguments` to be `null` and is
// redundant. Once upon a time, some awkward code was written f(g, null, // redundant. Once upon a time, some awkward code was written f(g, null,
// null, cb) ... // null, cb) ...
// callback = pays; callback = pays;
taker = options.taker; taker = options.taker;
pays = options.pays; pays = options.pays || options.taker_pays;
gets = options.gets; gets = options.gets || options.taker_gets;
} }
var lastArg = arguments[arguments.length - 1];
if (typeof lastArg === 'function') { if (typeof lastArg === 'function') {
callback = lastArg; callback = lastArg;
} }
@@ -1582,10 +1587,14 @@ Remote.prototype.requestSubmit = function(callback) {
* @api private * @api private
*/ */
Remote.prototype._serverPrepareSubscribe = function(callback) { Remote.prototype._serverPrepareSubscribe = function(server, callback) {
var self = this; var self = this;
var feeds = [ 'ledger', 'server' ]; var feeds = [ 'ledger', 'server' ];
if (typeof server === 'function') {
callback = server;
}
if (this._transaction_subs) { if (this._transaction_subs) {
feeds.push('transactions'); feeds.push('transactions');
} }
@@ -1606,16 +1615,17 @@ Remote.prototype._serverPrepareSubscribe = function(callback) {
self.emit('random', utils.hexToArray(message.random)); self.emit('random', utils.hexToArray(message.random));
} }
if (message.ledger_hash && message.ledger_index) { self._handleLedgerClosed(message, server);
self._ledger_time = message.ledger_time;
self._ledger_hash = message.ledger_hash;
self._ledger_current_index = message.ledger_index+1;
self.emit('ledger_closed', message);
}
self.emit('subscribed'); self.emit('subscribed');
}; };
request.on('error', function(err) {
if (self.trace) {
log.info('Initial server subscribe failed', err);
}
});
request.once('success', serverSubscribed); request.once('success', serverSubscribed);
self.emit('prepare_subscribe', request); self.emit('prepare_subscribe', request);
@@ -1650,12 +1660,13 @@ Remote.prototype.requestLedgerAccept = function(callback) {
request.callback(callback, 'ledger_closed'); request.callback(callback, 'ledger_closed');
request.request(); request.request();
return this; return request;
}; };
/** /**
* Account root request abstraction * Account root request abstraction
* *
* @this Remote
* @api private * @api private
*/ */
@@ -1675,7 +1686,7 @@ Remote.accountRootRequest = function(type, responseFilter, account, ledger, call
var request = this.requestLedgerEntry('account_root'); var request = this.requestLedgerEntry('account_root');
request.accountRoot(account); request.accountRoot(account);
request.ledgerChoose(ledger); request.ledgerSelect(ledger);
request.once('success', function(message) { request.once('success', function(message) {
request.emit(type, responseFilter(message)); request.emit(type, responseFilter(message));
@@ -2035,7 +2046,7 @@ Remote.prepareCurrencies = function(currency) {
} }
if (currency.hasOwnProperty('currency')) { if (currency.hasOwnProperty('currency')) {
newCurrency.currency = Currency.json_rewrite(currency.currency); newCurrency.currency = Currency.json_rewrite(currency.currency, {force_hex:true});
} }
return newCurrency; return newCurrency;
@@ -2213,65 +2224,40 @@ Remote.prototype.requestConnect = function(ip, port, callback) {
/** /**
* Create a Transaction * Create a Transaction
* *
* @param {String} source * @param {String} TransactionType
* @param {Object} options * @param {Object} options
* @param [Function] callback * @return {Transaction}
* @return {Request}
*/ */
Remote.prototype.transaction = Remote.prototype.transaction =
Remote.prototype.createTransaction = function(source, options, callback) { Remote.prototype.createTransaction = function(type, options) {
if (arguments.length === 0) {
// Fallback
return new Transaction(this);
}
assert.strictEqual(typeof type, 'string', 'TransactionType must be a string');
assert.strictEqual(typeof options, 'object', 'Transaction options must be an object');
var transaction = new Transaction(this); var transaction = new Transaction(this);
var transactionTypes = { var transactionTypes = {
payment: 'payment', payment: 'payment',
accountset: 'accountSet', accountset: 'accountSet',
trustset: 'trustSet', trustset: 'trustSet',
offercreate: 'offerCreate', offercreate: 'offerCreate',
offercancel: 'offerCancel', offercancel: 'offerCancel',
claim: 'claim', setregularkey: 'setRegularKey',
passwordfund: 'passwordFund', sign: 'sign'
passwordset: 'passwordSet',
setregularkey: 'setRegularKey',
walletadd: 'walletAdd',
sign: 'sign'
}; };
var transactionType; var transactionConstructor = transactionTypes[type.toLowerCase()];
switch (typeof source) { if (!transactionConstructor) {
case 'object': throw new Error('Invalid transaction type: ' + type);
if (typeof source.type !== 'string') {
throw new Error('Missing transaction type');
}
transactionType = transactionTypes[source.type.toLowerCase()];
if (!transactionType) {
throw new Error('Invalid transaction type: ' + transactionType);
}
transaction = transaction[transactionType](source);
break;
case 'string':
transactionType = transactionTypes[source.toLowerCase()];
if (!transactionType) {
throw new Error('Invalid transaction type: ' + transactionType);
}
transaction = transaction[transactionType](options);
break;
} }
var lastArg = arguments[arguments.length - 1]; return transaction[transactionConstructor](options);
if (typeof lastArg === 'function') {
transaction.submit(lastArg);
}
return transaction;
}; };
/** /**
@@ -2284,7 +2270,7 @@ Remote.prototype.createTransaction = function(source, options, callback) {
*/ */
Remote.prototype.feeTx = function(units) { Remote.prototype.feeTx = function(units) {
var server = this._getServer(); var server = this.getServer();
if (!server) { if (!server) {
throw new Error('No connected servers'); throw new Error('No connected servers');
@@ -2303,7 +2289,7 @@ Remote.prototype.feeTx = function(units) {
*/ */
Remote.prototype.feeTxUnit = function() { Remote.prototype.feeTxUnit = function() {
var server = this._getServer(); var server = this.getServer();
if (!server) { if (!server) {
throw new Error('No connected servers'); throw new Error('No connected servers');
@@ -2322,7 +2308,7 @@ Remote.prototype.feeTxUnit = function() {
*/ */
Remote.prototype.reserve = function(owner_count) { Remote.prototype.reserve = function(owner_count) {
var server = this._getServer(); var server = this.getServer();
if (!server) { if (!server) {
throw new Error('No connected servers'); throw new Error('No connected servers');

View File

@@ -33,23 +33,27 @@ function Request(remote, command) {
util.inherits(Request, EventEmitter); util.inherits(Request, EventEmitter);
Request.prototype.broadcast = function() { Request.prototype.broadcast = function() {
this._broadcast = true; var connectedServers = this.remote.getConnectedServers();
return this.request(); this.request(connectedServers);
return connectedServers.length;
}; };
// Send the request to a remote. // Send the request to a remote.
Request.prototype.request = function(callback) { Request.prototype.request = function(servers, callback) {
if (this.requested) { if (this.requested) {
return this; return this;
} }
this.requested = true; if (typeof servers === 'function') {
callback = servers;
}
this.requested = true;
this.on('error', function(){}); this.on('error', function(){});
this.emit('request', this.remote); this.emit('request', this.remote);
if (this._broadcast) { if (Array.isArray(servers)) {
this.remote._servers.forEach(function(server) { servers.forEach(function(server) {
this.setServer(server); this.setServer(server);
this.remote.request(this); this.remote.request(this);
}, this); }, this);
@@ -204,6 +208,13 @@ Request.prototype.ledgerIndex = function(ledger_index) {
return this; 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) { Request.prototype.ledgerSelect = function(ledger) {
switch (ledger) { switch (ledger) {
case 'current': case 'current':
@@ -213,10 +224,10 @@ Request.prototype.ledgerSelect = function(ledger) {
break; break;
default: default:
if (isNaN(ledger)) { if (Number(ledger)) {
this.message.ledger_hash = ledger; this.message.ledger_index = Number(ledger);
} else if ((ledger = Number(ledger))) { } else if (/^[A-F0-9]+$/.test(ledger)) {
this.message.ledger_index = ledger; this.message.ledger_hash = ledger;
} }
break; break;
} }

View File

@@ -16,7 +16,7 @@ var KeyPair = require('./keypair').KeyPair;
var Seed = extend(function () { var Seed = extend(function () {
// Internal form: NaN or BigInteger // Internal form: NaN or BigInteger
this._curve = sjcl.ecc.curves.c256; this._curve = sjcl.ecc.curves.k256;
this._value = NaN; this._value = NaN;
}, UInt); }, UInt);
@@ -33,8 +33,8 @@ Seed.prototype.parse_json = function (j) {
// XXX Should actually always try and continue if it failed. // 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); this._value = Base.decode_check(Base.VER_FAMILY_SEED, j);
} else if (j.length === 32) { } else if (/^[0-9a-fA-f]{32}$/.test(j)) {
this._value = this.parse_hex(j); this.parse_hex(j);
// XXX Should also try 1751 // XXX Should also try 1751
} else { } else {
this.parse_passphrase(j); this.parse_passphrase(j);
@@ -93,9 +93,14 @@ function SHA256_RIPEMD160(bits) {
* *
* {Uint160} (from_json able), specifies the address matching the KeyPair * {Uint160} (from_json able), specifies the address matching the KeyPair
* that is desired. * that is desired.
*
* @param maxLoops (optional)
* {Number} specifies the amount of attempts taken to generate
* a matching KeyPair
*/ */
Seed.prototype.get_key = function (account) { Seed.prototype.get_key = function (account, maxLoops) {
var account_number = 0, address; var account_number = 0, address;
var max_loops = maxLoops || 1;
if (!this.is_valid()) { if (!this.is_valid()) {
throw new Error('Cannot generate keys from invalid seed!'); throw new Error('Cannot generate keys from invalid seed!');
@@ -103,6 +108,7 @@ Seed.prototype.get_key = function (account) {
if (account) { if (account) {
if (typeof account === 'number') { if (typeof account === 'number') {
account_number = account; account_number = account;
max_loops = account_number+1;
} else { } else {
address = UInt160.from_json(account); address = UInt160.from_json(account);
} }
@@ -121,9 +127,9 @@ Seed.prototype.get_key = function (account) {
var sec; var sec;
var key_pair; var key_pair;
var max_loops = 1000; // TODO
do { do {
i = 0; i = 0;
do { do {
@@ -135,15 +141,15 @@ Seed.prototype.get_key = function (account) {
sec = sec.add(private_gen).mod(curve.r); sec = sec.add(private_gen).mod(curve.r);
key_pair = KeyPair.from_bn_secret(sec); key_pair = KeyPair.from_bn_secret(sec);
if (--max_loops <= 0) { if (max_loops-- <= 0) {
// We are almost certainly looking for an account that would take same // We are almost certainly looking for an account that would take same
// value of $too_long {forever, ...} // value of $too_long {forever, ...}
throw new Error('Too many loops looking for KeyPair yielding '+ throw new Error('Too many loops looking for KeyPair yielding '+
address.to_json() +' from ' + this.to_json()); address.to_json() +' from ' + this.to_json());
}; }
} while (address && !key_pair.get_address().equals(address));
return key_pair; } while (address && !key_pair.get_address().equals(address));
return key_pair;
}; };
exports.Seed = Seed; exports.Seed = Seed;

View File

@@ -42,7 +42,7 @@ function SerializedObject(buf) {
SerializedObject.from_json = function(obj) { SerializedObject.from_json = function(obj) {
// Create a copy of the object so we don't modify it // Create a copy of the object so we don't modify it
var obj = extend({}, obj); var obj = extend(true, {}, obj);
var so = new SerializedObject(); var so = new SerializedObject();
var typedef; var typedef;

View File

@@ -24,6 +24,7 @@ var Currency = amount.Currency;
// Shortcuts // Shortcuts
var hex = sjcl.codec.hex; var hex = sjcl.codec.hex;
var bytes = sjcl.codec.bytes; var bytes = sjcl.codec.bytes;
var utf8 = sjcl.codec.utf8String;
var BigInteger = utils.jsbn.BigInteger; var BigInteger = utils.jsbn.BigInteger;
@@ -52,7 +53,7 @@ function isBigInteger(val) {
return val instanceof BigInteger; return val instanceof BigInteger;
}; };
function serialize_hex(so, hexData, noLength) { function serializeHex(so, hexData, noLength) {
var byteData = bytes.fromBits(hex.toBits(hexData)); var byteData = bytes.fromBits(hex.toBits(hexData));
if (!noLength) { if (!noLength) {
SerializedType.serialize_varint(so, byteData.length); SerializedType.serialize_varint(so, byteData.length);
@@ -63,10 +64,18 @@ function serialize_hex(so, hexData, noLength) {
/** /**
* parses bytes as hex * parses bytes as hex
*/ */
function convert_bytes_to_hex (byte_array) { function convertByteArrayToHex (byte_array) {
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array)).toUpperCase(); return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array)).toUpperCase();
}; };
function convertStringToHex(string) {
return hex.fromBits(utf8.toBits(string)).toUpperCase();
}
function convertHexToString(hexString) {
return utf8.fromBits(hex.toBits(hexString));
}
SerializedType.serialize_varint = function (so, val) { SerializedType.serialize_varint = function (so, val) {
if (val < 0) { if (val < 0) {
throw new Error('Variable integers are unsigned.'); throw new Error('Variable integers are unsigned.');
@@ -115,9 +124,9 @@ SerializedType.prototype.parse_varint = function (so) {
* *
* 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) { function convertIntegerToByteArray(val, bytes) {
if (!isNumber(val)) { if (!isNumber(val)) {
throw new Error('Value is not a number'); throw new Error('Value is not a number', bytes);
} }
if (val < 0 || val >= Math.pow(256, bytes)) { if (val < 0 || val >= Math.pow(256, bytes)) {
@@ -130,7 +139,7 @@ function append_byte_array(so, val, bytes) {
newBytes.unshift(val >>> (i * 8) & 0xff); newBytes.unshift(val >>> (i * 8) & 0xff);
} }
so.append(newBytes); return newBytes;
}; };
// Convert a certain number of bytes from the serialized object ('so') into an integer. // Convert a certain number of bytes from the serialized object ('so') into an integer.
@@ -152,7 +161,7 @@ function readAndSum(so, bytes) {
var STInt8 = exports.Int8 = new SerializedType({ var STInt8 = exports.Int8 = new SerializedType({
serialize: function (so, val) { serialize: function (so, val) {
append_byte_array(so, val, 1); so.append(convertIntegerToByteArray(val, 1));
}, },
parse: function (so) { parse: function (so) {
return readAndSum(so, 1); return readAndSum(so, 1);
@@ -163,7 +172,7 @@ STInt8.id = 16;
var STInt16 = exports.Int16 = new SerializedType({ var STInt16 = exports.Int16 = new SerializedType({
serialize: function (so, val) { serialize: function (so, val) {
append_byte_array(so, val, 2); so.append(convertIntegerToByteArray(val, 2));
}, },
parse: function (so) { parse: function (so) {
return readAndSum(so, 2); return readAndSum(so, 2);
@@ -174,7 +183,7 @@ STInt16.id = 1;
var STInt32 = exports.Int32 = new SerializedType({ var STInt32 = exports.Int32 = new SerializedType({
serialize: function (so, val) { serialize: function (so, val) {
append_byte_array(so, val, 4); so.append(convertIntegerToByteArray(val, 4));
}, },
parse: function (so) { parse: function (so) {
return readAndSum(so, 4); return readAndSum(so, 4);
@@ -217,7 +226,7 @@ var STInt64 = exports.Int64 = new SerializedType({
hex = '0' + hex; hex = '0' + hex;
} }
serialize_hex(so, hex, true); //noLength = true serializeHex(so, hex, true); //noLength = true
}, },
parse: function (so) { parse: function (so) {
var bytes = so.read(8); var bytes = so.read(8);
@@ -237,7 +246,7 @@ var STHash128 = exports.Hash128 = new SerializedType({
if (!hash.is_valid()) { if (!hash.is_valid()) {
throw new Error('Invalid Hash128'); throw new Error('Invalid Hash128');
} }
serialize_hex(so, hash.to_hex(), true); //noLength = true serializeHex(so, hash.to_hex(), true); //noLength = true
}, },
parse: function (so) { parse: function (so) {
return UInt128.from_bytes(so.read(16)); return UInt128.from_bytes(so.read(16));
@@ -252,7 +261,7 @@ var STHash256 = exports.Hash256 = new SerializedType({
if (!hash.is_valid()) { if (!hash.is_valid()) {
throw new Error('Invalid Hash256'); throw new Error('Invalid Hash256');
} }
serialize_hex(so, hash.to_hex(), true); //noLength = true serializeHex(so, hash.to_hex(), true); //noLength = true
}, },
parse: function (so) { parse: function (so) {
return UInt256.from_bytes(so.read(32)); return UInt256.from_bytes(so.read(32));
@@ -267,7 +276,7 @@ var STHash160 = exports.Hash160 = new SerializedType({
if (!hash.is_valid()) { if (!hash.is_valid()) {
throw new Error('Invalid Hash160'); throw new Error('Invalid Hash160');
} }
serialize_hex(so, hash.to_hex(), true); //noLength = true serializeHex(so, hash.to_hex(), true); //noLength = true
}, },
parse: function (so) { parse: function (so) {
return UInt160.from_bytes(so.read(20)); return UInt160.from_bytes(so.read(20));
@@ -294,7 +303,7 @@ var STCurrency = new SerializedType({
// UInt160 value and consider it valid. But it doesn't, so for the // 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. // deserialization to be usable, we need to allow invalid results for now.
//if (!currency.is_valid()) { //if (!currency.is_valid()) {
// throw new Error('Invalid currency: '+convert_bytes_to_hex(bytes)); // throw new Error('Invalid currency: '+convertByteArrayToHex(bytes));
//} //}
return currency; return currency;
} }
@@ -345,7 +354,7 @@ var STAmount = exports.Amount = new SerializedType({
// Next eight bits: offset/exponent // Next eight bits: offset/exponent
hi |= ((97 + amount._offset) & 0xff) << 22; hi |= ((97 + amount._offset) & 0xff) << 22;
// Remaining 52 bits: mantissa // Remaining 54 bits: mantissa
hi |= amount._value.shiftRight(32).intValue() & 0x3fffff; hi |= amount._value.shiftRight(32).intValue() & 0x3fffff;
lo = amount._value.intValue() & 0xffffffff; lo = amount._value.intValue() & 0xffffffff;
} }
@@ -409,15 +418,16 @@ STAmount.id = 6;
var STVL = exports.VariableLength = exports.VL = new SerializedType({ var STVL = exports.VariableLength = exports.VL = new SerializedType({
serialize: function (so, val) { serialize: function (so, val) {
if (typeof val === 'string') { if (typeof val === 'string') {
serialize_hex(so, val); serializeHex(so, val);
} else { } else {
throw new Error('Unknown datatype.'); throw new Error('Unknown datatype.');
} }
}, },
parse: function (so) { parse: function (so) {
var len = this.parse_varint(so); var len = this.parse_varint(so);
return convert_bytes_to_hex(so.read(len)); return convertByteArrayToHex(so.read(len));
} }
}); });
@@ -429,7 +439,7 @@ var STAccount = exports.Account = new SerializedType({
if (!account.is_valid()) { if (!account.is_valid()) {
throw new Error('Invalid account!'); throw new Error('Invalid account!');
} }
serialize_hex(so, account.to_hex()); serializeHex(so, account.to_hex());
}, },
parse: function (so) { parse: function (so) {
var len = this.parse_varint(so); var len = this.parse_varint(so);
@@ -441,7 +451,6 @@ var STAccount = exports.Account = new SerializedType({
var result = UInt160.from_bytes(so.read(len)); var result = UInt160.from_bytes(so.read(len));
result.set_version(Base.VER_ACCOUNT_ID); result.set_version(Base.VER_ACCOUNT_ID);
//console.log('PARSED 160:', result.to_json());
if (false && !result.is_valid()) { if (false && !result.is_valid()) {
throw new Error('Invalid Account'); throw new Error('Invalid Account');
} }
@@ -593,6 +602,105 @@ var STVector256 = exports.Vector256 = new SerializedType({
STVector256.id = 19; STVector256.id = 19;
// Internal
var STMemo = exports.STMemo = new SerializedType({
serialize: function(so, val, no_marker) {
var keys = [];
Object.keys(val).forEach(function (key) {
// Ignore lowercase field names - they're non-serializable fields by
// convention.
if (key[0] === key[0].toLowerCase()) {
return;
}
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
throw new Error('JSON contains unknown field: "' + key + '"');
}
keys.push(key);
});
// Sort fields
keys = sort_fields(keys);
// store that we're dealing with json
var isJson = val.MemoFormat === 'json';
for (var i=0; i<keys.length; i++) {
var key = keys[i];
var value = val[key];
switch (key) {
// MemoType and MemoFormat are always ASCII strings
case 'MemoType':
case 'MemoFormat':
value = convertStringToHex(value);
break;
// MemoData can be a JSON object, otherwise it's a string
case 'MemoData':
if (typeof value !== 'string') {
if (isJson) {
try {
value = convertStringToHex(JSON.stringify(value));
} catch (e) {
throw new Error('MemoFormat json with invalid JSON in MemoData field');
}
} else {
throw new Error('MemoData can only be a JSON object with a valid json MemoFormat');
}
} else if (isString(value)) {
value = convertStringToHex(value);
}
break;
}
serialize(so, key, value);
}
if (!no_marker) {
//Object ending marker
STInt8.serialize(so, 0xe1);
}
},
parse: function(so) {
var output = {};
while (so.peek(1)[0] !== 0xe1) {
var keyval = parse(so);
output[keyval[0]] = keyval[1];
}
if (output['MemoType'] !== void(0)) {
output['parsed_memo_type'] = convertHexToString(output['MemoType']);
}
if (output['MemoFormat'] !== void(0)) {
output['parsed_memo_format'] = convertHexToString(output['MemoFormat']);
}
if (output['MemoData'] !== void(0)) {
// see if we can parse JSON
if (output['parsed_memo_format'] === 'json') {
try {
output['parsed_memo_data'] = JSON.parse(convertHexToString(output['MemoData']));
} catch(e) {
// fail, which is fine, we just won't add the memo_data field
}
} else if(output['parsed_memo_format'] === 'text') {
output['parsed_memo_data'] = convertHexToString(output['MemoData']);
}
}
so.read(1);
return output;
}
});
exports.serialize = exports.serialize_whatever = serialize; exports.serialize = exports.serialize_whatever = serialize;
function serialize(so, field_name, value) { function serialize(so, field_name, value) {
@@ -622,10 +730,22 @@ function serialize(so, field_name, value) {
STInt8.serialize(so, field_bits); STInt8.serialize(so, field_bits);
} }
// Get the serializer class (ST...) for a field based on the type bits. // Get the serializer class (ST...)
var serialized_object_type = exports[binformat.types[type_bits]]; var serialized_object_type;
//do something with val[keys] and val[keys[i]]; if (field_name === 'Memo' && typeof value === 'object') {
serialized_object_type.serialize(so, value); // for Memo we override the default behavior with our STMemo serializer
serialized_object_type = exports.STMemo;
} else {
// for a field based on the type bits.
serialized_object_type = exports[binformat.types[type_bits]];
}
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. //Take the serialized object, figure out what type/field it is, and return the parsing of that.
@@ -639,18 +759,21 @@ function parse(so) {
type_bits = so.read(1)[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_bits = tag_byte & 0x0f;
var field_name = (field_bits === 0) var field_name = (field_bits === 0)
? field_name = binformat.fields[type_bits][so.read(1)[0]] ? field_name = binformat.fields[type_bits][so.read(1)[0]]
: field_name = binformat.fields[type_bits][field_bits]; : field_name = binformat.fields[type_bits][field_bits];
assert(field_name, 'Unknown field - header byte is 0x' + tag_byte.toString(16)); assert(field_name, 'Unknown field - header byte is 0x' + tag_byte.toString(16));
// Get the parser class (ST...) for a field based on the type bits.
var type = (field_name === 'Memo')
? exports.STMemo
: exports[binformat.types[type_bits]];
assert(type, 'Unknown type - header byte is 0x' + tag_byte.toString(16));
return [ field_name, type.parse(so) ]; //key, value return [ field_name, type.parse(so) ]; //key, value
}; };
@@ -672,18 +795,20 @@ function sort_fields(keys) {
var STObject = exports.Object = new SerializedType({ var STObject = exports.Object = new SerializedType({
serialize: function (so, val, no_marker) { serialize: function (so, val, no_marker) {
var keys = Object.keys(val); var keys = [];
// Ignore lowercase field names - they're non-serializable fields by Object.keys(val).forEach(function (key) {
// convention. // Ignore lowercase field names - they're non-serializable fields by
keys = keys.filter(function (key) { // convention.
return key[0] !== key[0].toLowerCase(); if (key[0] === key[0].toLowerCase()) {
}); return;
}
keys.forEach(function (key) {
if (typeof binformat.fieldsInverseMap[key] === 'undefined') { if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
throw new Error('JSON contains unknown field: "' + key + '"'); throw new Error('JSON contains unknown field: "' + key + '"');
} }
keys.push(key);
}); });
// Sort fields // Sort fields

View File

@@ -2,7 +2,6 @@ var util = require('util');
var url = require('url'); var url = require('url');
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
var Amount = require('./amount').Amount; var Amount = require('./amount').Amount;
var Transaction = require('./transaction').Transaction;
var log = require('./log').internal.sub('server'); var log = require('./log').internal.sub('server');
/** /**
@@ -51,53 +50,48 @@ function Server(remote, opts) {
} }
this._remote = remote; this._remote = remote;
this._opts = opts; this._opts = opts;
this._ws = void(0); this._ws = void(0);
this._connected = false; this._connected = false;
this._shouldConnect = false; this._shouldConnect = false;
this._state = 'offline'; this._state = 'offline';
this._id = 0; this._id = 0; // request ID
this._retry = 0; this._retry = 0;
this._requests = { }; this._requests = { };
this._load_base = 256; this._load_base = 256;
this._load_factor = 256; this._load_factor = 256;
this._fee = 10; this._fee = 10;
this._fee_ref = 10; this._fee_ref = 10;
this._fee_base = 10; this._fee_base = 10;
this._reserve_base = void(0); this._reserve_base = void(0);
this._reserve_inc = void(0); this._reserve_inc = void(0);
this._fee_cushion = this._remote.fee_cushion; this._fee_cushion = this._remote.fee_cushion;
this._lastLedgerIndex = NaN; this._lastLedgerIndex = NaN;
this._lastLedgerClose = NaN; this._lastLedgerClose = NaN;
this._score = 0; this._score = 0;
this._scoreWeights = { this._scoreWeights = {
ledgerclose: 5, ledgerclose: 5,
response: 1 response: 1
}; };
this._pubkey_node = '';
this._url = this._opts.url = (this._opts.secure ? 'wss://' : 'ws://') this._url = this._opts.url = (this._opts.secure ? 'wss://' : 'ws://')
+ this._opts.host + ':' + this._opts.port; + this._opts.host + ':' + this._opts.port;
this._hostid = ''; this.on('message', function onMessage(message) {
function onMessage(message) {
self._handleMessage(message); self._handleMessage(message);
}; });
this.on('message', onMessage); this.on('response_subscribe', function onSubscribe(message) {
function onSubscribeResponse(message) {
self._handleResponseSubscribe(message); self._handleResponseSubscribe(message);
}; });
this.on('response_subscribe', onSubscribeResponse);
function setActivityInterval() { function setActivityInterval() {
var interval = self._checkActivity.bind(self); var interval = self._checkActivity.bind(self);
@@ -111,27 +105,37 @@ function Server(remote, opts) {
this.once('ledger_closed', setActivityInterval); this.once('ledger_closed', setActivityInterval);
this._remote.on('ledger_closed', function(ledger) { this._remote.on('ledger_closed', function onRemoteLedgerClose(ledger) {
self._updateScore('ledgerclose', ledger); self._updateScore('ledgerclose', ledger);
}); });
this.on('response_ping', function(message, request) { this.on('response_ping', function onPingResponse(message, request) {
self._updateScore('response', request); self._updateScore('response', request);
}); });
this.on('load_changed', function(load) { this.on('load_changed', function onLoadChange(load) {
self._updateScore('loadchange', load); self._updateScore('loadchange', load);
}); });
this.on('response_server_info', function(message) { // If server is not up-to-date, request server_info
try { // for getting pubkey_node & hostid information.
self._hostid = '(' + message.info.pubkey_node + ')'; // Otherwise this information is available on the
} catch (e) { // initial server subscribe response
this.on('connect', function requestServerID() {
if (self._pubkey_node) {
return;
} }
});
this.on('connect', function() { self.on('response_server_info', function setServerID(message) {
self._request(self._remote.requestServerInfo()); try {
self._pubkey_node = message.info.pubkey_node;
} catch (e) {
}
});
var serverInfoRequest = self._remote.requestServerInfo();
serverInfoRequest.on('error', function() { });
self._request(serverInfoRequest);
}); });
}; };
@@ -181,7 +185,7 @@ Server.websocketConstructor = function() {
Server.prototype._setState = function(state) { Server.prototype._setState = function(state) {
if (state !== this._state) { if (state !== this._state) {
if (this._remote.trace) { if (this._remote.trace) {
log.info('set_state:', this._opts.url, this._hostid, state); log.info(this.getServerID(), 'set_state:', state);
} }
this._state = state; this._state = state;
@@ -213,7 +217,7 @@ Server.prototype._setState = function(state) {
*/ */
Server.prototype._checkActivity = function() { Server.prototype._checkActivity = function() {
if (!this._connected) { if (!this.isConnected()) {
return; return;
} }
@@ -224,7 +228,9 @@ Server.prototype._checkActivity = function() {
var delta = (Date.now() - this._lastLedgerClose); var delta = (Date.now() - this._lastLedgerClose);
if (delta > (1000 * 25)) { if (delta > (1000 * 25)) {
log.info('reconnect: activity delta:', delta); if (this._remote.trace) {
log.info(this.getServerID(), 'reconnect: activity delta:', delta);
}
this.reconnect(); this.reconnect();
} }
}; };
@@ -242,7 +248,7 @@ Server.prototype._checkActivity = function() {
*/ */
Server.prototype._updateScore = function(type, data) { Server.prototype._updateScore = function(type, data) {
if (!this._connected) { if (!this.isConnected()) {
return; return;
} }
@@ -268,7 +274,9 @@ Server.prototype._updateScore = function(type, data) {
} }
if (this._score > 1e3) { if (this._score > 1e3) {
log.info('reconnect: score:', this._score); if (this._remote.trace) {
log.info(this.getServerID(), 'reconnect: score:', this._score);
}
this.reconnect(); this.reconnect();
} }
}; };
@@ -293,8 +301,9 @@ Server.prototype._remoteAddress = function() {
* Get the server's hostid * Get the server's hostid
*/ */
Server.prototype.getHostID = function() { Server.prototype.getHostID =
return this._hostid; Server.prototype.getServerID = function() {
return this._url + ' (' + (this._pubkey_node ? this._pubkey_node : '') + ')';
}; };
/** /**
@@ -306,7 +315,7 @@ Server.prototype.getHostID = function() {
Server.prototype.disconnect = function() { Server.prototype.disconnect = function() {
var self = this; var self = this;
if (!this._connected) { if (!this.isConnected()) {
this.once('socket_open', function() { this.once('socket_open', function() {
self.disconnect(); self.disconnect();
}); });
@@ -345,7 +354,7 @@ Server.prototype.reconnect = function() {
if (this.isConnected()) { if (this.isConnected()) {
this.once('disconnect', reconnect); this.once('disconnect', reconnect);
this.disconnect(); this.disconnect();
} else { } else {
reconnect(); reconnect();
} }
} }
@@ -372,7 +381,7 @@ Server.prototype.connect = function() {
// recently received a message from the server and the WebSocket has not // recently received a message from the server and the WebSocket has not
// reported any issues either. If we do fail to ping or the connection drops, // reported any issues either. If we do fail to ping or the connection drops,
// we will automatically reconnect. // we will automatically reconnect.
if (this._connected) { if (this.isConnected()) {
return; return;
} }
@@ -382,7 +391,7 @@ Server.prototype.connect = function() {
} }
if (this._remote.trace) { if (this._remote.trace) {
log.info('connect:', this._opts.url, this._hostid); log.info(this.getServerID(), 'connect');
} }
var ws = this._ws = new WebSocket(this._opts.url); var ws = this._ws = new WebSocket(this._opts.url);
@@ -399,7 +408,7 @@ Server.prototype.connect = function() {
if (ws === self._ws) { if (ws === self._ws) {
self.emit('socket_open'); self.emit('socket_open');
// Subscribe to events // Subscribe to events
self._request(self._remote._serverPrepareSubscribe()); self._request(self._remote._serverPrepareSubscribe(self));
} }
}; };
@@ -408,7 +417,7 @@ Server.prototype.connect = function() {
self.emit('socket_error'); self.emit('socket_error');
if (self._remote.trace) { if (self._remote.trace) {
log.info('onerror:', self._opts.url, self._hostid, e.data || e); log.info(self.getServerID(), 'onerror:', e.data || e);
} }
// Most connection errors for WebSockets are conveyed as 'close' events with // Most connection errors for WebSockets are conveyed as 'close' events with
@@ -432,7 +441,7 @@ Server.prototype.connect = function() {
ws.onclose = function onClose() { ws.onclose = function onClose() {
if (ws === self._ws) { if (ws === self._ws) {
if (self._remote.trace) { if (self._remote.trace) {
log.info('onclose:', self._opts.url, self._hostid, ws.readyState); log.info(self.getServerID(), 'onclose:', ws.readyState);
} }
self._handleClose(); self._handleClose();
} }
@@ -465,7 +474,7 @@ Server.prototype._retryConnect = function() {
function connectionRetry() { function connectionRetry() {
if (self._shouldConnect) { if (self._shouldConnect) {
if (self._remote.trace) { if (self._remote.trace) {
log.info('retry', self._opts.url, self._hostid); log.info(self.getServerID(), 'retry', self._retry);
} }
self.connect(); self.connect();
} }
@@ -543,6 +552,7 @@ Server.prototype._handleServerStatus = function(message) {
// This message is only received when online. // This message is only received when online.
// As we are connected, it is the definitive final state. // As we are connected, it is the definitive final state.
var isOnline = ~Server.onlineStates.indexOf(message.server_status); var isOnline = ~Server.onlineStates.indexOf(message.server_status);
this._setState(isOnline ? 'online' : 'offline'); this._setState(isOnline ? 'online' : 'offline');
if (!Server.isLoadStatus(message)) { if (!Server.isLoadStatus(message)) {
@@ -553,7 +563,7 @@ Server.prototype._handleServerStatus = function(message) {
this._remote.emit('load', message, this); this._remote.emit('load', message, this);
var loadChanged = message.load_base !== this._load_base var loadChanged = message.load_base !== this._load_base
|| message.load_factor !== this._load_factor || message.load_factor !== this._load_factor;
if (loadChanged) { if (loadChanged) {
this._load_base = message.load_base; this._load_base = message.load_base;
@@ -571,14 +581,14 @@ Server.prototype._handleResponse = function(message) {
if (!request) { if (!request) {
if (this._remote.trace) { if (this._remote.trace) {
log.info('UNEXPECTED:', this._opts.url, this._hostid, message); log.info(this.getServerID(), 'UNEXPECTED:', message);
} }
return; return;
} }
if (message.status === 'success') { if (message.status === 'success') {
if (this._remote.trace) { if (this._remote.trace) {
log.info('response:', this._opts.url, this._hostid, message); log.info(this.getServerID(), 'response:', message);
} }
var command = request.message.command; var command = request.message.command;
@@ -592,22 +602,20 @@ Server.prototype._handleResponse = function(message) {
}); });
} else if (message.error) { } else if (message.error) {
if (this._remote.trace) { if (this._remote.trace) {
log.info('error:', this._opts.url, this._hostid, message); log.info(this.getServerID(), 'error:', message);
} }
var error = { request.emit('error', {
error: 'remoteError', error: 'remoteError',
error_message: 'Remote reported an error.', error_message: 'Remote reported an error.',
remote: message remote: message
}; });
request.emit('error', error);
} }
}; };
Server.prototype._handlePathFind = function(message) { Server.prototype._handlePathFind = function(message) {
if (this._remote.trace) { if (this._remote.trace) {
log.info('path_find:', this._opts.url, this._hostid, message); log.info(this.getServerID(), 'path_find:', message);
} }
}; };
@@ -615,27 +623,53 @@ Server.prototype._handlePathFind = function(message) {
* Handle subscription response messages. Subscription response * Handle subscription response messages. Subscription response
* messages indicate that a connection to the server is ready * messages indicate that a connection to the server is ready
* *
* @param {Object} message
* @api private * @api private
*/ */
Server.prototype._handleResponseSubscribe = function(message) { Server.prototype._handleResponseSubscribe = function(message) {
if (~(Server.onlineStates.indexOf(message.server_status))) { if (!this._remote._allow_partial_history
this._setState('online'); && !Server.hasFullLedgerHistory(message)) {
// Server has partial history and Remote has been configured to disallow
// servers with incomplete history
return this.reconnect();
}
if (message.pubkey_node) {
// pubkey_node is used to identify the server
this._pubkey_node = message.pubkey_node;
} }
if (Server.isLoadStatus(message)) { if (Server.isLoadStatus(message)) {
this._load_base = message.load_base || 256; this._load_base = message.load_base || 256;
this._load_factor = message.load_factor || 256; this._load_factor = message.load_factor || 256;
this._fee_ref = message.fee_ref; this._fee_ref = message.fee_ref || 10;
this._fee_base = message.fee_base; this._fee_base = message.fee_base || 10;
this._reserve_base = message.reserve_base; this._reserve_base = message.reserve_base;
this._reserve_inc = message.reserve_inc; this._reserve_inc = message.reserve_inc;
} }
if (~Server.onlineStates.indexOf(message.server_status)) {
this._setState('online');
}
};
/**
* Check that server message indicates that server has complete ledger history
*
* @param {Object} message
* @return {Boolean}
*/
Server.hasFullLedgerHistory = function(message) {
return (typeof message === 'object')
&& (message.server_status === 'full')
&& (typeof message.validated_ledgers === 'string')
&& (message.validated_ledgers.split('-').length === 2);
}; };
/** /**
* Check that received message from rippled is valid * Check that received message from rippled is valid
* *
* @api private * @param {Object} message
* @return {Boolean}
*/ */
Server.isValidMessage = function(message) { Server.isValidMessage = function(message) {
@@ -644,14 +678,15 @@ Server.isValidMessage = function(message) {
}; };
/** /**
* Check that received serverStatus message contains * Check that received serverStatus message contains load status information
* load status information
* *
* @api private * @param {Object} message
* @return {Boolean}
*/ */
Server.isLoadStatus = function(message) { Server.isLoadStatus = function(message) {
return (typeof message.load_base === 'number') return (typeof message === 'object')
&& (typeof message.load_base === 'number')
&& (typeof message.load_factor === 'number'); && (typeof message.load_factor === 'number');
}; };
@@ -665,17 +700,17 @@ Server.isLoadStatus = function(message) {
Server.prototype._sendMessage = function(message) { Server.prototype._sendMessage = function(message) {
if (this._ws) { if (this._ws) {
if (this._remote.trace) { if (this._remote.trace) {
log.info('request:', this._opts.url, this._hostid, message); log.info(this.getServerID(), 'request:', message);
} }
this._ws.send(JSON.stringify(message)); this._ws.send(JSON.stringify(message));
} }
}; };
/** /**
* Submit a Request object. * Submit a Request object
* *
* Requests are indexed by message ID, which is repeated * Requests are indexed by message ID, which is repeated in the response from
* in the response from rippled WebSocket server * rippled WebSocket server
* *
* @param {Request} request * @param {Request} request
* @api private * @api private
@@ -687,7 +722,7 @@ Server.prototype._request = function(request) {
// Only bother if we are still connected. // Only bother if we are still connected.
if (!this._ws) { if (!this._ws) {
if (this._remote.trace) { if (this._remote.trace) {
log.info('request: DROPPING:', self._opts.url, self._hostid, request.message); log.info(this.getServerID(), 'request: DROPPING:', request.message);
} }
return; return;
} }
@@ -724,22 +759,16 @@ Server.prototype._isConnected = function() {
* Calculate transaction fee * Calculate transaction fee
* *
* @param {Transaction|Number} Fee units for a provided transaction * @param {Transaction|Number} Fee units for a provided transaction
* @return {Number} Final fee in XRP for specified number of fee units * @return {String} Final fee in XRP for specified number of fee units
* @api private * @api private
*/ */
Server.prototype._computeFee = function(transaction) { Server.prototype._computeFee = function(feeUnits) {
var units; if (isNaN(feeUnits)) {
if (transaction instanceof Transaction) {
units = transaction._getFeeUnits();
} else if (typeof transaction === 'number') {
units = transaction;
} else {
throw new Error('Invalid argument'); throw new Error('Invalid argument');
} }
return this._feeTx(units).to_json(); return this._feeTx(Number(feeUnits)).to_json();
}; };
/** /**

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,20 @@
var LRU = require('lru-cache');
var Transaction = require('./transaction').Transaction;
/** /**
* Manager for pending transactions * Manager for pending transactions
*/ */
var LRU = require('lru-cache');
var Transaction = require('./transaction').Transaction;
function TransactionQueue() { function TransactionQueue() {
this._queue = [ ]; this._queue = [ ];
this._idCache = LRU(); this._idCache = LRU({ max: 200 });
this._sequenceCache = LRU(); this._sequenceCache = LRU({ max: 200 });
}; };
/** /**
* Store received (validated) sequence * Store received (validated) sequence
*
* @param {Number} sequence
*/ */
TransactionQueue.prototype.addReceivedSequence = function(sequence) { TransactionQueue.prototype.addReceivedSequence = function(sequence) {
@@ -23,6 +24,9 @@ TransactionQueue.prototype.addReceivedSequence = function(sequence) {
/** /**
* Check that sequence number has been consumed by a validated * Check that sequence number has been consumed by a validated
* transaction * transaction
*
* @param {Number} sequence
* @return {Boolean}
*/ */
TransactionQueue.prototype.hasSequence = function(sequence) { TransactionQueue.prototype.hasSequence = function(sequence) {
@@ -31,6 +35,9 @@ TransactionQueue.prototype.hasSequence = function(sequence) {
/** /**
* Store received (validated) ID transaction * Store received (validated) ID transaction
*
* @param {String} transaction id
* @param {Transaction} transaction
*/ */
TransactionQueue.prototype.addReceivedId = function(id, transaction) { TransactionQueue.prototype.addReceivedId = function(id, transaction) {
@@ -39,6 +46,9 @@ TransactionQueue.prototype.addReceivedId = function(id, transaction) {
/** /**
* Get received (validated) transaction by ID * Get received (validated) transaction by ID
*
* @param {String} transaction id
* @return {Object}
*/ */
TransactionQueue.prototype.getReceived = function(id) { TransactionQueue.prototype.getReceived = function(id) {
@@ -48,6 +58,9 @@ TransactionQueue.prototype.getReceived = function(id) {
/** /**
* Get a submitted transaction by ID. Transactions * Get a submitted transaction by ID. Transactions
* may have multiple associated IDs. * may have multiple associated IDs.
*
* @param {String} transaction id
* @return {Transaction}
*/ */
TransactionQueue.prototype.getSubmission = function(id) { TransactionQueue.prototype.getSubmission = function(id) {
@@ -63,8 +76,32 @@ TransactionQueue.prototype.getSubmission = function(id) {
return result; return result;
}; };
/**
* Get earliest ledger in the pending queue
*
* @return {Number} ledger
*/
TransactionQueue.prototype.getMinLedger = function() {
var result = Infinity;
for (var i=0, tx; (tx=this._queue[i]); i++) {
if (tx.initialSubmitIndex < result) {
result = tx.initialSubmitIndex;
}
}
if (!isFinite(result)) {
result = -1;
}
return result;
};
/** /**
* Remove a transaction from the queue * Remove a transaction from the queue
*
* @param {String|Transaction} transaction or id
*/ */
TransactionQueue.prototype.remove = function(tx) { TransactionQueue.prototype.remove = function(tx) {
@@ -87,14 +124,30 @@ TransactionQueue.prototype.remove = function(tx) {
} }
}; };
/**
* Add a transaction to pending queue
*
* @param {Transaction} transaction
*/
TransactionQueue.prototype.push = function(tx) { TransactionQueue.prototype.push = function(tx) {
this._queue.push(tx); this._queue.push(tx);
}; };
/**
* Iterate over pending transactions
*
* @param {Function} iterator
*/
TransactionQueue.prototype.forEach = function(fn) { TransactionQueue.prototype.forEach = function(fn) {
this._queue.forEach(fn); this._queue.forEach(fn);
}; };
/**
* @return {Number} length of pending queue
*/
TransactionQueue.prototype.length = TransactionQueue.prototype.length =
TransactionQueue.prototype.getLength = function() { TransactionQueue.prototype.getLength = function() {
return this._queue.length; return this._queue.length;

View File

@@ -575,8 +575,6 @@ VaultClient.prototype.generateDeviceID = function () {
VaultClient.prototype.resendEmail = blobClient.resendEmail; VaultClient.prototype.resendEmail = blobClient.resendEmail;
VaultClient.prototype.updateProfile = blobClient.updateProfile;
VaultClient.prototype.recoverBlob = blobClient.recoverBlob; VaultClient.prototype.recoverBlob = blobClient.recoverBlob;
VaultClient.prototype.deleteBlob = blobClient.deleteBlob; VaultClient.prototype.deleteBlob = blobClient.deleteBlob;
@@ -585,5 +583,11 @@ VaultClient.prototype.requestToken = blobClient.requestToken;
VaultClient.prototype.verifyToken = blobClient.verifyToken; VaultClient.prototype.verifyToken = blobClient.verifyToken;
VaultClient.prototype.getAttestation = blobClient.getAttestation;
VaultClient.prototype.updateAttestation = blobClient.updateAttestation;
VaultClient.prototype.getAttestationSummary = blobClient.getAttestationSummary;
//export by name //export by name
exports.VaultClient = VaultClient; exports.VaultClient = VaultClient;

8
src/js/ripple/wallet.js Normal file
View File

@@ -0,0 +1,8 @@
var sjcl = require('./utils').sjcl;
var WalletGenerator = require('ripple-wallet-generator')({
sjcl: sjcl
});
module.exports = WalletGenerator;

View File

@@ -65,7 +65,7 @@ sjcl.ecc.ecdsa.secretKey.prototype.signWithRecoverablePublicKey = function(hash,
* *
* @param {bitArray} hash * @param {bitArray} hash
* @param {bitArray} signature * @param {bitArray} signature
* @param {sjcl.ecc.curve} [sjcl.ecc.curves['c256']] curve * @param {sjcl.ecc.curve} [sjcl.ecc.curves['k256']] curve
* @returns {sjcl.ecc.ecdsa.publicKey} Public key * @returns {sjcl.ecc.ecdsa.publicKey} Public key
*/ */
sjcl.ecc.ecdsa.publicKey.recoverFromSignature = function(hash, signature, curve) { sjcl.ecc.ecdsa.publicKey.recoverFromSignature = function(hash, signature, curve) {
@@ -75,7 +75,7 @@ sjcl.ecc.ecdsa.publicKey.recoverFromSignature = function(hash, signature, curve)
} }
if (!curve) { if (!curve) {
curve = sjcl.ecc.curves['c256']; curve = sjcl.ecc.curves['k256'];
} }
// Convert hash to bits and determine encoding for output // Convert hash to bits and determine encoding for output

View File

@@ -1,17 +1,16 @@
// ----- for secp256k1 ------ // ----- for secp256k1 ------
// Overwrite NIST-P256 with secp256k1 sjcl.ecc.point.prototype.toBytesCompressed = function () {
sjcl.ecc.curves.c256 = new sjcl.ecc.curve( var header = this.y.mod(2).toString() == "0x0" ? 0x02 : 0x03;
sjcl.bn.pseudoMersennePrime(256, [[0,-1],[4,-1],[6,-1],[7,-1],[8,-1],[9,-1],[32,-1]]), return [header].concat(sjcl.codec.bytes.fromBits(this.x.toBits()))
"0x14551231950b75fc4402da1722fc9baee", };
0,
7,
"0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"
);
// Replace point addition and doubling algorithms // Replace point addition and doubling algorithms
// NIST-P256 is a=-3, we need algorithms for a=0 // NIST-P256 is a=-3, we need algorithms for a=0
//
// This is a custom point addition formula that
// only works for a=-3 Jacobian curve. It's much
// faster than the generic implementation
sjcl.ecc.pointJac.prototype.add = function(T) { sjcl.ecc.pointJac.prototype.add = function(T) {
var S = this; var S = this;
if (S.curve !== T.curve) { if (S.curve !== T.curve) {
@@ -43,7 +42,7 @@ sjcl.ecc.pointJac.prototype.add = function(T) {
var j = h.mul(i); var j = h.mul(i);
var r = s2.sub(S.y).doubleM(); var r = s2.sub(S.y).doubleM();
var v = S.x.mul(i); var v = S.x.mul(i);
var x = r.square().subM(j).subM(v.copy().doubleM()); var x = r.square().subM(j).subM(v.copy().doubleM());
var y = r.mul(v.sub(x)).subM(S.y.mul(j).doubleM()); var y = r.mul(v.sub(x)).subM(S.y.mul(j).doubleM());
var z = S.z.add(h).square().subM(z1z1).subM(hh); var z = S.z.add(h).square().subM(z1z1).subM(hh);
@@ -51,6 +50,9 @@ sjcl.ecc.pointJac.prototype.add = function(T) {
return new sjcl.ecc.pointJac(this.curve,x,y,z); return new sjcl.ecc.pointJac(this.curve,x,y,z);
}; };
// This is a custom doubling algorithm that
// only works for a=-3 Jacobian curve. It's much
// faster than the generic implementation
sjcl.ecc.pointJac.prototype.doubl = function () { sjcl.ecc.pointJac.prototype.doubl = function () {
if (this.isIdentity) { return this; } if (this.isIdentity) { return this; }
@@ -66,7 +68,9 @@ sjcl.ecc.pointJac.prototype.doubl = function () {
return new sjcl.ecc.pointJac(this.curve, x, y, z); return new sjcl.ecc.pointJac(this.curve, x, y, z);
}; };
sjcl.ecc.point.prototype.toBytesCompressed = function () { // DEPRECATED:
var header = this.y.mod(2).toString() == "0x0" ? 0x02 : 0x03; // previously the c256 curve was overridden with the secp256k1 curve
return [header].concat(sjcl.codec.bytes.fromBits(this.x.toBits())) // since then, sjcl has been updated to support k256
}; // this override exist to keep supporting the old c256 with k256 behavior
// this will be removed in future release
sjcl.ecc.curves.c256 = sjcl.ecc.curves.k256;

2
src/js/sjcl/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
core.js
core_closure.js

9
src/js/sjcl/.travis.yml Normal file
View File

@@ -0,0 +1,9 @@
before_script: ./configure --with-all
language: node_js
node_js:
- "0.11"
- "0.10"
- "0.8"
# 0.6 appears to be unreliable on Travis at the moment.
# - "0.6"

View File

@@ -54,10 +54,11 @@ lint: core.js core/*.js test/*.js browserTest/*.js lint/coding_guidelines.pl
lint/coding_guidelines.pl core/*.js test/*.js browserTest/*.js lint/coding_guidelines.pl core/*.js test/*.js browserTest/*.js
TEST_COMMON= browserTest/rhinoUtil.js test/test.js TEST_COMMON= browserTest/nodeUtil.js test/test.js
TEST_SCRIPTS= $(TEST_COMMON) \ TEST_SCRIPTS= $(TEST_COMMON) \
test/aes_vectors.js test/aes_test.js \ test/aes_vectors.js test/aes_test.js \
test/bitArray_vectors.js test/bitArray_test.js \
test/ocb2_vectors.js test/ocb2_test.js \ test/ocb2_vectors.js test/ocb2_test.js \
test/ccm_vectors.js test/ccm_test.js \ test/ccm_vectors.js test/ccm_test.js \
test/cbc_vectors.js test/cbc_test.js \ test/cbc_vectors.js test/cbc_test.js \
@@ -70,18 +71,14 @@ TEST_SCRIPTS= $(TEST_COMMON) \
test/hmac_vectors.js test/hmac_test.js \ test/hmac_vectors.js test/hmac_test.js \
test/pbkdf2_test.js \ test/pbkdf2_test.js \
test/bn_vectors.js test/bn_test.js \ test/bn_vectors.js test/bn_test.js \
test/ecdsa_test.js test/ecdsa_vectors.js test/ecdh_test.js test/ecdsa_test.js test/ecdsa_vectors.js test/ecdh_test.js \
test/srp_vectors.js test/srp_test.js \
test/json_test.js
TEST_SCRIPTS_OPT= $(TEST_COMMON) \ # Run all tests in node.js.
test/srp_vectors.js test/srp_test.js
# Rhino fails at -O 0. Probably because the big files full of test vectors blow the test: sjcl.js $(TEST_SCRIPTS) test/run_tests_node.js
# bytecode limit. So, run most tests with -O -1. But modular exponentiation is node test/run_tests_node.js $< $(TEST_SCRIPTS)
# currently very slow (on Rhino), so run the SRP test with optimizations on.
test: sjcl.js $(TEST_SCRIPTS) test/run_tests_rhino.js
@rhino -O -1 -w test/run_tests_rhino.js $< $(TEST_SCRIPTS)
@rhino -O 9 -w test/run_tests_rhino.js $< $(TEST_SCRIPTS_OPT)
tidy: tidy:
find . -name '*~' -delete find . -name '*~' -delete

21
src/js/sjcl/README.md Normal file
View File

@@ -0,0 +1,21 @@
sjcl
====
[![Build Status](https://travis-ci.org/bitwiseshiftleft/sjcl.png)](https://travis-ci.org/bitwiseshiftleft/sjcl)
Stanford Javascript Crypto Library
Security Advisories
===
* 12.02.2014: the current development version has a paranoia bug in the ecc module. The bug was introduced in commit [ac0b3fe0](https://github.com/bitwiseshiftleft/sjcl/commit/ac0b3fe0) and might affect ecc key generation on platforms without a platform random number generator.
*
Security Contact
====
Security Mail: sjcl@ovt.me
OpenPGP-Key Fingerprint: 0D54 3E52 87B4 EC06 3FA9 0115 72ED A6C7 7AAF 48ED
Keyserver: pool.sks-keyservers.net
Documentation
====
The documentation is available [here](http://bitwiseshiftleft.github.io/sjcl/doc/)

12
src/js/sjcl/bower.json Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "sjcl",
"version": "1.0.0",
"main": ["./sjcl.js"],
"ignore": [
"**/*",
"!README.md",
"!README/*",
"!bower.json",
"!sjcl.js"
]
}

View File

@@ -1,6 +1,6 @@
browserUtil = {}; browserUtil = {};
browserUtil.isRhino = (typeof(window) === 'undefined'); browserUtil.isNodeJS = (typeof(window) === 'undefined');
/** /**
* Pause (for the graphics to update and the script timer to clear), then run the * Pause (for the graphics to update and the script timer to clear), then run the

View File

@@ -0,0 +1,88 @@
<html>
<head>
<title>Entropy Generator Progress</title>
<!-- ProgressBar source: http://stackoverflow.com/questions/7190898/progress-bar-with-html-and-css -->
<style>
#progressbar {
background-color: black;
border-radius: 13px; /* (height of inner div) / 2 + padding */
padding: 3px;
}
#progressbar > div {
background-color: orange;
width: 0%; /* Adjust with JavaScript */
height: 20px;
border-radius: 10px;
}
</style>
<script type="text/javascript" src="../sjcl.js">
</script>
<script type="text/javascript">
var busy = 0;
var collecting = 0;
function showprogress () {
var barwidth = document.getElementById ("progresswidth");
var paranoia = parseInt (document.getElementById ("paranoialevel").value);
var progress = 100 * sjcl.random.getProgress (paranoia);
barwidth.style.width = progress+"%";
if (!sjcl.random.isReady (paranoia)) {
setTimeout ("showprogress()", 10, "JavaScript");
} else {
busy = 0;
document.getElementById ("startbutton").style.disabled = 1;
}
}
function startup () {
if (collecting == 0) {
sjcl.random.startCollectors ();
collecting = 1;
}
if (busy == 0) {
busy = 1;
document.getElementById ("startbutton").style.disabled = 1;
showprogress ();
}
}
function consume (numbits) {
var collector = document.getElementById ("collector");
collector.value = "retrieving random data";
var paranoia = document.getElementById ("paranoialevel").value;
var numwords = Math.ceil (numbits / 32);
var bits = sjcl.random.randomWords (numwords, paranoia);
collector.value = '';
for (var i=0; i<numwords; i++) {
var hi = (bits [i] >> 16) & 0x0000ffff;
var lo = bits [i] & 0x0000ffff;
collector.value = collector.value + hi.toString (16) + lo.toString (16);
}
startup ();
}
</script>
</head>
<body>
<h1>Entropy Generator Progress</h1>
<p>Target: 192 bits, available at paranoia level 5.</p>
<p>Corresponding paranoia level from [0,1..10]: <input type="text" value="5" id="paranoialevel"/> <input type=button onclick="startup ()" id="startbutton" value=" Start &gt;&gt; "> (the idea being that you can see the progress bar advance gently from empty/black to full/yellow after you press this)</p>
<p><input type=button onclick="consume (192)" value=" Consume 192 bits &gt;&gt; "><input type=text id=collector size=50 value="" onkeypress="consume (192)"> (also consumes 192 bits with every keypress in the text field; use key repeat to consume swiftly)</p>
<div id="progressbar">
<div id="progresswidth"></div>
</div>
<p>Please move your mouse, play around and generally introduce entropy into your environment.</p>
</body>
</html>

View File

@@ -0,0 +1,44 @@
browserUtil = {
isNodeJS: true,
pauseAndThen: function (cb) { cb(); },
cpsIterate: function (f, start, end, pause, callback) {
function go() {
var called = false;
if (start >= end) {
callback && callback();
} else {
f(start, function () {
if (!called) { called = true; start++; go(); }
});
}
}
go (start);
},
cpsMap: function (map, list, pause, callback) {
browserUtil.cpsIterate(function (i, cb) { map(list[i], i, list.length, cb); },
0, list.length, pause, callback);
},
loadScripts: function(scriptNames, callback) {
for (i=0; i<scriptNames.length; i++) {
load(scriptNames[i]);
callback && callback();
}
},
write: function(type, message) {
console.log(message);
return { update: function (type2, message2) {
if (type2 === 'pass') { console.log(" + " + message2); }
else if (type2 === 'unimplemented') { console.log(" ? " + message2); }
else { console.log(" - " + message2); }
}};
},
writeNewline: function () { console.log(""); },
status: function(message) {}
};

View File

@@ -16,7 +16,7 @@ sub digitize {
} }
while (<>) { while (<>) {
s/([^a-zA-Z0-9_])(\d+)/$1 . digitize $2/eg; s/([^a-zA-Z0-9_"])(\d+)/$1 . digitize $2/eg;
print; print;
} }

11
src/js/sjcl/configure vendored
View File

@@ -4,12 +4,13 @@ use strict;
my ($arg, $i, $j, $targ); my ($arg, $i, $j, $targ);
my @targets = qw/sjcl aes bitArray codecString codecHex codecBase64 codecBytes sha256 sha512 sha1 ccm cbc ocb2 gcm hmac pbkdf2 random convenience bn ecc srp/; my @targets = qw/sjcl aes bitArray codecString codecHex codecBase32 codecBase64 codecBytes sha256 sha512 sha1 ccm cbc ocb2 gcm hmac pbkdf2 random convenience bn ecc srp/;
my %deps = ('aes'=>'sjcl', my %deps = ('aes'=>'sjcl',
'bitArray'=>'sjcl', 'bitArray'=>'sjcl',
'codecString'=>'bitArray', 'codecString'=>'bitArray',
'codecHex'=>'bitArray', 'codecHex'=>'bitArray',
'codecBase64'=>'bitArray', 'codecBase64'=>'bitArray',
'codecBase32'=>'bitArray',
'codecBytes'=>'bitArray', 'codecBytes'=>'bitArray',
'sha256'=>'codecString', 'sha256'=>'codecString',
'sha512'=>'codecString', 'sha512'=>'codecString',
@@ -32,10 +33,10 @@ my %enabled = ();
$enabled{$_} = 0 foreach (@targets); $enabled{$_} = 0 foreach (@targets);
# by default, all but codecBytes, srp, bn # by default, all but codecBytes, srp, bn
$enabled{$_} = 1 foreach (qw/aes bitArray codecString codecHex codecBase64 sha256 ccm ocb2 gcm hmac pbkdf2 random convenience/); $enabled{$_} = 1 foreach (qw/aes bitArray codecString codecHex codecBase32 codecBase64 sha256 ccm ocb2 gcm hmac pbkdf2 random convenience/);
# argument parsing # argument parsing
while ($arg = shift @ARGV) { while (my $arg = shift @ARGV) {
if ($arg =~ /^--?with-all$/) { if ($arg =~ /^--?with-all$/) {
foreach (@targets) { foreach (@targets) {
if ($enabled{$_} == 0) { if ($enabled{$_} == 0) {
@@ -97,7 +98,7 @@ my $config = '';
my $pconfig; my $pconfig;
# dependency analysis: forbidden # dependency analysis: forbidden
foreach $i (@targets) { foreach my $i (@targets) {
if ($enabled{$i} > 0) { if ($enabled{$i} > 0) {
foreach $j (split /,/, $deps{$i}) { foreach $j (split /,/, $deps{$i}) {
if ($enabled{$j} == -1) { if ($enabled{$j} == -1) {
@@ -114,7 +115,7 @@ foreach $i (@targets) {
} }
# reverse # reverse
foreach $i (reverse @targets) { foreach my $i (reverse @targets) {
if ($enabled{$i} > 0) { if ($enabled{$i} > 0) {
foreach $j (split /,/, $deps{$i}) { foreach $j (split /,/, $deps{$i}) {
if ($enabled{$j} < $enabled{$i}) { if ($enabled{$j} < $enabled{$i}) {

View File

@@ -74,7 +74,7 @@ sjcl.bitArray = {
return a1.concat(a2); return a1.concat(a2);
} }
var out, i, last = a1[a1.length-1], shift = sjcl.bitArray.getPartial(last); var last = a1[a1.length-1], shift = sjcl.bitArray.getPartial(last);
if (shift === 32) { if (shift === 32) {
return a1.concat(a2); return a1.concat(a2);
} else { } else {
@@ -183,5 +183,19 @@ sjcl.bitArray = {
*/ */
_xor4: function(x,y) { _xor4: function(x,y) {
return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]]; return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]];
},
/** byteswap a word array inplace.
* (does not handle partial words)
* @param {sjcl.bitArray} a word array
* @return {sjcl.bitArray} byteswapped array
*/
byteswapM: function(a) {
var i, v, m = 0xff00;
for (i = 0; i < a.length; ++i) {
v = a[i];
a[i] = (v >>> 24) | ((v >>> 8) & m) | ((v & m) << 8) | (v << 24);
}
return a;
} }
}; };

View File

@@ -19,7 +19,7 @@ sjcl.bn.prototype = {
* Initializes this with it, either as a bn, a number, or a hex string. * Initializes this with it, either as a bn, a number, or a hex string.
*/ */
initWith: function(it) { initWith: function(it) {
var i=0, k, n, l; var i=0, k;
switch(typeof it) { switch(typeof it) {
case "object": case "object":
this.limbs = it.limbs.slice(0); this.limbs = it.limbs.slice(0);
@@ -328,7 +328,7 @@ sjcl.bn.prototype = {
carry = (l-m)*ipv; carry = (l-m)*ipv;
} }
if (carry === -1) { if (carry === -1) {
limbs[i-1] -= this.placeVal; limbs[i-1] -= pv;
} }
return this; return this;
}, },
@@ -370,7 +370,9 @@ sjcl.bn.prototype = {
} }
}; };
/** @this { sjcl.bn } */ /** @memberOf sjcl.bn
* @this { sjcl.bn }
*/
sjcl.bn.fromBits = function(bits) { sjcl.bn.fromBits = function(bits) {
var Class = this, out = new Class(), words=[], w=sjcl.bitArray, t = this.prototype, var Class = this, out = new Class(), words=[], w=sjcl.bitArray, t = this.prototype,
l = Math.min(this.bitLength || 0x100000000, w.bitLength(bits)), e = l % t.radix || t.radix; l = Math.min(this.bitLength || 0x100000000, w.bitLength(bits)), e = l % t.radix || t.radix;
@@ -394,7 +396,9 @@ sjcl.bn.prototype.radixMask = (1 << sjcl.bn.prototype.radix) - 1;
* i.e. a prime of the form 2^e + sum(a * 2^b),where the sum is negative and sparse. * i.e. a prime of the form 2^e + sum(a * 2^b),where the sum is negative and sparse.
*/ */
sjcl.bn.pseudoMersennePrime = function(exponent, coeff) { sjcl.bn.pseudoMersennePrime = function(exponent, coeff) {
/** @constructor */ /** @constructor
* @private
*/
function p(it) { function p(it) {
this.initWith(it); this.initWith(it);
/*if (this.limbs[this.modOffset]) { /*if (this.limbs[this.modOffset]) {
@@ -427,10 +431,11 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) {
ppr.modulus.cnormalize(); ppr.modulus.cnormalize();
/** Approximate reduction mod p. May leave a number which is negative or slightly larger than p. /** Approximate reduction mod p. May leave a number which is negative or slightly larger than p.
* @this {sjcl.bn} * @memberof sjcl.bn
* @this { sjcl.bn }
*/ */
ppr.reduce = function() { ppr.reduce = function() {
var i, k, l, mo = this.modOffset, limbs = this.limbs, aff, off = this.offset, ol = this.offset.length, fac = this.factor, ll; var i, k, l, mo = this.modOffset, limbs = this.limbs, off = this.offset, ol = this.offset.length, fac = this.factor, ll;
i = this.minOffset; i = this.minOffset;
while (limbs.length > mo) { while (limbs.length > mo) {
@@ -452,7 +457,9 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) {
return this; return this;
}; };
/** @this {sjcl.bn} */ /** @memberof sjcl.bn
* @this { sjcl.bn }
*/
ppr._strongReduce = (ppr.fullMask === -1) ? ppr.reduce : function() { ppr._strongReduce = (ppr.fullMask === -1) ? ppr.reduce : function() {
var limbs = this.limbs, i = limbs.length - 1, k, l; var limbs = this.limbs, i = limbs.length - 1, k, l;
this.reduce(); this.reduce();
@@ -467,7 +474,8 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) {
}; };
/** mostly constant-time, very expensive full reduction. /** mostly constant-time, very expensive full reduction.
* @this {sjcl.bn} * @memberof sjcl.bn
* @this { sjcl.bn }
*/ */
ppr.fullReduce = function() { ppr.fullReduce = function() {
var greater, i; var greater, i;
@@ -501,7 +509,9 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) {
}; };
/** @this {sjcl.bn} */ /** @memberof sjcl.bn
* @this { sjcl.bn }
*/
ppr.inverse = function() { ppr.inverse = function() {
return (this.power(this.modulus.sub(2))); return (this.power(this.modulus.sub(2)));
}; };
@@ -512,18 +522,24 @@ sjcl.bn.pseudoMersennePrime = function(exponent, coeff) {
}; };
// a small Mersenne prime // a small Mersenne prime
var sbp = sjcl.bn.pseudoMersennePrime;
sjcl.bn.prime = { sjcl.bn.prime = {
p127: sjcl.bn.pseudoMersennePrime(127, [[0,-1]]), p127: sbp(127, [[0,-1]]),
// Bernstein's prime for Curve25519 // Bernstein's prime for Curve25519
p25519: sjcl.bn.pseudoMersennePrime(255, [[0,-19]]), p25519: sbp(255, [[0,-19]]),
// Koblitz primes
p192k: sbp(192, [[32,-1],[12,-1],[8,-1],[7,-1],[6,-1],[3,-1],[0,-1]]),
p224k: sbp(224, [[32,-1],[12,-1],[11,-1],[9,-1],[7,-1],[4,-1],[1,-1],[0,-1]]),
p256k: sbp(256, [[32,-1],[9,-1],[8,-1],[7,-1],[6,-1],[4,-1],[0,-1]]),
// NIST primes // NIST primes
p192: sjcl.bn.pseudoMersennePrime(192, [[0,-1],[64,-1]]), p192: sbp(192, [[0,-1],[64,-1]]),
p224: sjcl.bn.pseudoMersennePrime(224, [[0,1],[96,-1]]), p224: sbp(224, [[0,1],[96,-1]]),
p256: sjcl.bn.pseudoMersennePrime(256, [[0,-1],[96,1],[192,1],[224,-1]]), p256: sbp(256, [[0,-1],[96,1],[192,1],[224,-1]]),
p384: sjcl.bn.pseudoMersennePrime(384, [[0,-1],[32,1],[96,-1],[128,-1]]), p384: sbp(384, [[0,-1],[32,1],[96,-1],[128,-1]]),
p521: sjcl.bn.pseudoMersennePrime(521, [[0,-1]]) p521: sbp(521, [[0,-1]])
}; };
sjcl.bn.random = function(modulus, paranoia) { sjcl.bn.random = function(modulus, paranoia) {

View File

@@ -5,18 +5,18 @@
* @author Dan Boneh * @author Dan Boneh
*/ */
/** @namespace
* Dangerous: CBC mode with PKCS#5 padding.
*
* @author Emily Stark
* @author Mike Hamburg
* @author Dan Boneh
*/
if (sjcl.beware === undefined) { if (sjcl.beware === undefined) {
sjcl.beware = {}; sjcl.beware = {};
} }
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity." sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."
] = function() { ] = function() {
/** @namespace
* Dangerous: CBC mode with PKCS#5 padding.
*
* @author Emily Stark
* @author Mike Hamburg
* @author Dan Boneh
*/
sjcl.mode.cbc = { sjcl.mode.cbc = {
/** The name of the mode. /** The name of the mode.
* @constant * @constant
@@ -100,7 +100,7 @@ sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity.
/* check and remove the pad */ /* check and remove the pad */
bi = output[i-1] & 255; bi = output[i-1] & 255;
if (bi == 0 || bi > 16) { if (bi === 0 || bi > 16) {
throw new sjcl.exception.corrupt("pkcs#5 padding corrupt"); throw new sjcl.exception.corrupt("pkcs#5 padding corrupt");
} }
bo = bi * 0x1010101; bo = bi * 0x1010101;

View File

@@ -25,7 +25,7 @@ sjcl.mode.ccm = {
* @return {bitArray} The encrypted data, an array of bytes. * @return {bitArray} The encrypted data, an array of bytes.
*/ */
encrypt: function(prf, plaintext, iv, adata, tlen) { encrypt: function(prf, plaintext, iv, adata, tlen) {
var L, i, out = plaintext.slice(0), tag, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(out) / 8; var L, out = plaintext.slice(0), tag, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(out) / 8;
tlen = tlen || 64; tlen = tlen || 64;
adata = adata || []; adata = adata || [];
@@ -59,7 +59,7 @@ sjcl.mode.ccm = {
decrypt: function(prf, ciphertext, iv, adata, tlen) { decrypt: function(prf, ciphertext, iv, adata, tlen) {
tlen = tlen || 64; tlen = tlen || 64;
adata = adata || []; adata = adata || [];
var L, i, var L,
w=sjcl.bitArray, w=sjcl.bitArray,
ivl = w.bitLength(iv) / 8, ivl = w.bitLength(iv) / 8,
ol = w.bitLength(ciphertext), ol = w.bitLength(ciphertext),
@@ -101,7 +101,7 @@ sjcl.mode.ccm = {
*/ */
_computeTag: function(prf, plaintext, iv, adata, tlen, L) { _computeTag: function(prf, plaintext, iv, adata, tlen, L) {
// compute B[0] // compute B[0]
var q, mac, field = 0, offset = 24, tmp, i, macData = [], w=sjcl.bitArray, xor = w._xor4; var mac, tmp, i, macData = [], w=sjcl.bitArray, xor = w._xor4;
tlen /= 8; tlen /= 8;
@@ -161,7 +161,7 @@ sjcl.mode.ccm = {
* @private * @private
*/ */
_ctrMode: function(prf, data, iv, tag, tlen, L) { _ctrMode: function(prf, data, iv, tag, tlen, L) {
var enc, i, w=sjcl.bitArray, xor = w._xor4, ctr, b, l = data.length, bl=w.bitLength(data); var enc, i, w=sjcl.bitArray, xor = w._xor4, ctr, l = data.length, bl=w.bitLength(data);
// start the ctr // start the ctr
ctr = w.concat([w.partial(8,L-1)],iv).concat([0,0,0]).slice(0,4); ctr = w.concat([w.partial(8,L-1)],iv).concat([0,0,0]).slice(0,4);

View File

@@ -0,0 +1,64 @@
/** @fileOverview Bit array codec implementations.
*
* @author Nils Kenneweg
*/
/** @namespace Base32 encoding/decoding */
sjcl.codec.base32 = {
/** The base32 alphabet.
* @private
*/
_chars: "0123456789abcdefghjkmnpqrstvwxyz",
/* bits in an array */
BITS: 32,
/* base to encode at (2^x) */
BASE: 5,
/* bits - base */
REMAINING: 27,
/** Convert from a bitArray to a base32 string. */
fromBits: function (arr, _noEquals) {
var BITS = sjcl.codec.base32.BITS, BASE = sjcl.codec.base32.BASE, REMAINING = sjcl.codec.base32.REMAINING;
var out = "", i, bits=0, c = sjcl.codec.base32._chars, ta=0, bl = sjcl.bitArray.bitLength(arr);
for (i=0; out.length * BASE <= bl; ) {
out += c.charAt((ta ^ arr[i]>>>bits) >>> REMAINING);
if (bits < BASE) {
ta = arr[i] << (BASE-bits);
bits += REMAINING;
i++;
} else {
ta <<= BASE;
bits -= BASE;
}
}
return out;
},
/** Convert from a base32 string to a bitArray */
toBits: function(str) {
var BITS = sjcl.codec.base32.BITS, BASE = sjcl.codec.base32.BASE, REMAINING = sjcl.codec.base32.REMAINING;
var out = [], i, bits=0, c = sjcl.codec.base32._chars, ta=0, x;
for (i=0; i<str.length; i++) {
x = c.indexOf(str.charAt(i));
if (x < 0) {
throw new sjcl.exception.invalid("this isn't base32!");
}
if (bits > REMAINING) {
bits -= REMAINING;
out.push(ta ^ x>>>bits);
ta = x << (BITS-bits);
} else {
bits += BASE;
ta ^= x << (BITS-bits);
}
}
if (bits&56) {
out.push(sjcl.bitArray.partial(bits&56, ta, 1));
}
return out;
}
};

View File

@@ -15,7 +15,9 @@ sjcl.codec.base64 = {
/** Convert from a bitArray to a base64 string. */ /** Convert from a bitArray to a base64 string. */
fromBits: function (arr, _noEquals, _url) { fromBits: function (arr, _noEquals, _url) {
var out = "", i, bits=0, c = sjcl.codec.base64._chars, ta=0, bl = sjcl.bitArray.bitLength(arr); var out = "", i, bits=0, c = sjcl.codec.base64._chars, ta=0, bl = sjcl.bitArray.bitLength(arr);
if (_url) c = c.substr(0,62) + '-_'; if (_url) {
c = c.substr(0,62) + '-_';
}
for (i=0; out.length * 6 < bl; ) { for (i=0; out.length * 6 < bl; ) {
out += c.charAt((ta ^ arr[i]>>>bits) >>> 26); out += c.charAt((ta ^ arr[i]>>>bits) >>> 26);
if (bits < 6) { if (bits < 6) {
@@ -35,7 +37,9 @@ sjcl.codec.base64 = {
toBits: function(str, _url) { toBits: function(str, _url) {
str = str.replace(/\s|=/g,''); str = str.replace(/\s|=/g,'');
var out = [], i, bits=0, c = sjcl.codec.base64._chars, ta=0, x; var out = [], i, bits=0, c = sjcl.codec.base64._chars, ta=0, x;
if (_url) c = c.substr(0,62) + '-_'; if (_url) {
c = c.substr(0,62) + '-_';
}
for (i=0; i<str.length; i++) { for (i=0; i<str.length; i++) {
x = c.indexOf(str.charAt(i)); x = c.indexOf(str.charAt(i));
if (x < 0) { if (x < 0) {

View File

@@ -9,7 +9,7 @@
sjcl.codec.hex = { sjcl.codec.hex = {
/** Convert from a bitArray to a hex string. */ /** Convert from a bitArray to a hex string. */
fromBits: function (arr) { fromBits: function (arr) {
var out = "", i, x; var out = "", i;
for (i=0; i<arr.length; i++) { for (i=0; i<arr.length; i++) {
out += ((arr[i]|0)+0xF00000000000).toString(16).substr(4); out += ((arr[i]|0)+0xF00000000000).toString(16).substr(4);
} }

View File

@@ -15,13 +15,13 @@
* @param {String} plaintext The data to encrypt. * @param {String} plaintext The data to encrypt.
* @param {Object} [params] The parameters including tag, iv and salt. * @param {Object} [params] The parameters including tag, iv and salt.
* @param {Object} [rp] A returned version with filled-in parameters. * @param {Object} [rp] A returned version with filled-in parameters.
* @return {String} The ciphertext. * @return {Object} The cipher raw data.
* @throws {sjcl.exception.invalid} if a parameter is invalid. * @throws {sjcl.exception.invalid} if a parameter is invalid.
*/ */
encrypt: function (password, plaintext, params, rp) { _encrypt: function (password, plaintext, params, rp) {
params = params || {}; params = params || {};
rp = rp || {}; rp = rp || {};
var j = sjcl.json, p = j._add({ iv: sjcl.random.randomWords(4,0) }, var j = sjcl.json, p = j._add({ iv: sjcl.random.randomWords(4,0) },
j.defaults), tmp, prp, adata; j.defaults), tmp, prp, adata;
j._add(p, params); j._add(p, params);
@@ -32,7 +32,7 @@
if (typeof p.iv === "string") { if (typeof p.iv === "string") {
p.iv = sjcl.codec.base64.toBits(p.iv); p.iv = sjcl.codec.base64.toBits(p.iv);
} }
if (!sjcl.mode[p.mode] || if (!sjcl.mode[p.mode] ||
!sjcl.cipher[p.cipher] || !sjcl.cipher[p.cipher] ||
(typeof password === "string" && p.iter <= 100) || (typeof password === "string" && p.iter <= 100) ||
@@ -41,7 +41,7 @@
(p.iv.length < 2 || p.iv.length > 4)) { (p.iv.length < 2 || p.iv.length > 4)) {
throw new sjcl.exception.invalid("json encrypt: invalid parameters"); throw new sjcl.exception.invalid("json encrypt: invalid parameters");
} }
if (typeof password === "string") { if (typeof password === "string") {
tmp = sjcl.misc.cachedPbkdf2(password, p); tmp = sjcl.misc.cachedPbkdf2(password, p);
password = tmp.key.slice(0,p.ks/32); password = tmp.key.slice(0,p.ks/32);
@@ -58,39 +58,52 @@
adata = sjcl.codec.utf8String.toBits(adata); adata = sjcl.codec.utf8String.toBits(adata);
} }
prp = new sjcl.cipher[p.cipher](password); prp = new sjcl.cipher[p.cipher](password);
/* return the json data */ /* return the json data */
j._add(rp, p); j._add(rp, p);
rp.key = password; rp.key = password;
/* do the encryption */ /* do the encryption */
p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, adata, p.ts); p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, adata, p.ts);
//return j.encode(j._subtract(p, j.defaults)); //return j.encode(j._subtract(p, j.defaults));
return p;
},
/** Simple encryption function.
* @param {String|bitArray} password The password or key.
* @param {String} plaintext The data to encrypt.
* @param {Object} [params] The parameters including tag, iv and salt.
* @param {Object} [rp] A returned version with filled-in parameters.
* @return {String} The ciphertext serialized data.
* @throws {sjcl.exception.invalid} if a parameter is invalid.
*/
encrypt: function (password, plaintext, params, rp) {
var j = sjcl.json, p = j._encrypt.apply(j, arguments);
return j.encode(p); return j.encode(p);
}, },
/** Simple decryption function. /** Simple decryption function.
* @param {String|bitArray} password The password or key. * @param {String|bitArray} password The password or key.
* @param {String} ciphertext The ciphertext to decrypt. * @param {Object} ciphertext The cipher raw data to decrypt.
* @param {Object} [params] Additional non-default parameters. * @param {Object} [params] Additional non-default parameters.
* @param {Object} [rp] A returned object with filled parameters. * @param {Object} [rp] A returned object with filled parameters.
* @return {String} The plaintext. * @return {String} The plaintext.
* @throws {sjcl.exception.invalid} if a parameter is invalid. * @throws {sjcl.exception.invalid} if a parameter is invalid.
* @throws {sjcl.exception.corrupt} if the ciphertext is corrupt. * @throws {sjcl.exception.corrupt} if the ciphertext is corrupt.
*/ */
decrypt: function (password, ciphertext, params, rp) { _decrypt: function (password, ciphertext, params, rp) {
params = params || {}; params = params || {};
rp = rp || {}; rp = rp || {};
var j = sjcl.json, p = j._add(j._add(j._add({},j.defaults),j.decode(ciphertext)), params, true), ct, tmp, prp, adata=p.adata; var j = sjcl.json, p = j._add(j._add(j._add({},j.defaults),ciphertext), params, true), ct, tmp, prp, adata=p.adata;
if (typeof p.salt === "string") { if (typeof p.salt === "string") {
p.salt = sjcl.codec.base64.toBits(p.salt); p.salt = sjcl.codec.base64.toBits(p.salt);
} }
if (typeof p.iv === "string") { if (typeof p.iv === "string") {
p.iv = sjcl.codec.base64.toBits(p.iv); p.iv = sjcl.codec.base64.toBits(p.iv);
} }
if (!sjcl.mode[p.mode] || if (!sjcl.mode[p.mode] ||
!sjcl.cipher[p.cipher] || !sjcl.cipher[p.cipher] ||
(typeof password === "string" && p.iter <= 100) || (typeof password === "string" && p.iter <= 100) ||
@@ -100,7 +113,7 @@
(p.iv.length < 2 || p.iv.length > 4)) { (p.iv.length < 2 || p.iv.length > 4)) {
throw new sjcl.exception.invalid("json decrypt: invalid parameters"); throw new sjcl.exception.invalid("json decrypt: invalid parameters");
} }
if (typeof password === "string") { if (typeof password === "string") {
tmp = sjcl.misc.cachedPbkdf2(password, p); tmp = sjcl.misc.cachedPbkdf2(password, p);
password = tmp.key.slice(0,p.ks/32); password = tmp.key.slice(0,p.ks/32);
@@ -112,15 +125,33 @@
adata = sjcl.codec.utf8String.toBits(adata); adata = sjcl.codec.utf8String.toBits(adata);
} }
prp = new sjcl.cipher[p.cipher](password); prp = new sjcl.cipher[p.cipher](password);
/* do the decryption */ /* do the decryption */
ct = sjcl.mode[p.mode].decrypt(prp, p.ct, p.iv, adata, p.ts); ct = sjcl.mode[p.mode].decrypt(prp, p.ct, p.iv, adata, p.ts);
/* return the json data */ /* return the json data */
j._add(rp, p); j._add(rp, p);
rp.key = password; rp.key = password;
return sjcl.codec.utf8String.fromBits(ct); if (params.raw === 1) {
return ct;
} else {
return sjcl.codec.utf8String.fromBits(ct);
}
},
/** Simple decryption function.
* @param {String|bitArray} password The password or key.
* @param {String} ciphertext The ciphertext to decrypt.
* @param {Object} [params] Additional non-default parameters.
* @param {Object} [rp] A returned object with filled parameters.
* @return {String} The plaintext.
* @throws {sjcl.exception.invalid} if a parameter is invalid.
* @throws {sjcl.exception.corrupt} if the ciphertext is corrupt.
*/
decrypt: function (password, ciphertext, params, rp) {
var j = sjcl.json;
return j._decrypt(password, j.decode(ciphertext), params, rp);
}, },
/** Encode a flat structure into a JSON string. /** Encode a flat structure into a JSON string.
@@ -138,23 +169,23 @@
} }
out += comma + '"' + i + '":'; out += comma + '"' + i + '":';
comma = ','; comma = ',';
switch (typeof obj[i]) { switch (typeof obj[i]) {
case 'number': case 'number':
case 'boolean': case 'boolean':
out += obj[i]; out += obj[i];
break; break;
case 'string': case 'string':
out += '"' + escape(obj[i]) + '"'; out += '"' + escape(obj[i]) + '"';
break; break;
case 'object': case 'object':
out += '"' + sjcl.codec.base64.fromBits(obj[i],0) + '"'; out += '"' + sjcl.codec.base64.fromBits(obj[i],0) + '"';
break; break;
default: default:
throw new sjcl.exception.bug("json encode: unsupported type"); throw new sjcl.exception.bug("json encode: unsupported type");
} }
} }
} }
@@ -174,13 +205,15 @@
} }
var a = str.replace(/^\{|\}$/g, '').split(/,/), out={}, i, m; var a = str.replace(/^\{|\}$/g, '').split(/,/), out={}, i, m;
for (i=0; i<a.length; i++) { for (i=0; i<a.length; i++) {
if (!(m=a[i].match(/^(?:(["']?)([a-z][a-z0-9]*)\1):(?:(\d+)|"([a-z0-9+\/%*_.@=\-]*)")$/i))) { if (!(m=a[i].match(/^\s*(?:(["']?)([a-z][a-z0-9]*)\1)\s*:\s*(?:(-?\d+)|"([a-z0-9+\/%*_.@=\-]*)"|(true|false))$/i))) {
throw new sjcl.exception.invalid("json decode: this isn't json!"); throw new sjcl.exception.invalid("json decode: this isn't json!");
} }
if (m[3]) { if (m[3]) {
out[m[2]] = parseInt(m[3],10); out[m[2]] = parseInt(m[3],10);
} else { } else if (m[4]) {
out[m[2]] = m[2].match(/^(ct|salt|iv)$/) ? sjcl.codec.base64.toBits(m[4]) : unescape(m[4]); out[m[2]] = m[2].match(/^(ct|salt|iv)$/) ? sjcl.codec.base64.toBits(m[4]) : unescape(m[4]);
} else if (m[5]) {
out[m[2]] = m[5] === 'true';
} }
} }
return out; return out;
@@ -213,13 +246,13 @@
*/ */
_subtract: function (plus, minus) { _subtract: function (plus, minus) {
var out = {}, i; var out = {}, i;
for (i in plus) { for (i in plus) {
if (plus.hasOwnProperty(i) && plus[i] !== minus[i]) { if (plus.hasOwnProperty(i) && plus[i] !== minus[i]) {
out[i] = plus[i]; out[i] = plus[i];
} }
} }
return out; return out;
}, },
@@ -262,7 +295,7 @@ sjcl.misc._pbkdf2Cache = {};
/** Cached PBKDF2 key derivation. /** Cached PBKDF2 key derivation.
* @param {String} password The password. * @param {String} password The password.
* @param {Object} [params] The derivation params (iteration count and optional salt). * @param {Object} [obj] The derivation params (iteration count and optional salt).
* @return {Object} The derived data in key, the salt in salt. * @return {Object} The derived data in key, the salt in salt.
*/ */
sjcl.misc.cachedPbkdf2 = function (password, obj) { sjcl.misc.cachedPbkdf2 = function (password, obj) {

View File

@@ -1,3 +1,6 @@
/**
* base class for all ecc operations.
*/
sjcl.ecc = {}; sjcl.ecc = {};
/** /**
@@ -152,7 +155,9 @@ sjcl.ecc.pointJac.prototype = {
a = y2.mul(this.x.mul(4)), a = y2.mul(this.x.mul(4)),
b = y2.square().mul(8), b = y2.square().mul(8),
z2 = this.z.square(), z2 = this.z.square(),
c = this.x.sub(z2).mul(3).mul(this.x.add(z2)), c = this.curve.a.toString() == (new sjcl.bn(-3)).toString() ?
this.x.sub(z2).mul(3).mul(this.x.add(z2)) :
this.x.square().mul(3).add(z2.square().mul(this.curve.a)),
x = c.square().subM(a).subM(a), x = c.square().subM(a).subM(a),
y = a.sub(x).mul(c).subM(b), y = a.sub(x).mul(c).subM(b),
z = this.y.add(this.y).mul(this.z); z = this.y.add(this.y).mul(this.z);
@@ -162,7 +167,7 @@ sjcl.ecc.pointJac.prototype = {
/** /**
* Returns a copy of this point converted to affine coordinates. * Returns a copy of this point converted to affine coordinates.
* @return {sjcl.ecc.point} The converted point. * @return {sjcl.ecc.point} The converted point.
*/ */
toAffine: function() { toAffine: function() {
if (this.isIdentity || this.z.equals(0)) { if (this.isIdentity || this.z.equals(0)) {
return new sjcl.ecc.point(this.curve); return new sjcl.ecc.point(this.curve);
@@ -250,7 +255,7 @@ sjcl.ecc.pointJac.prototype = {
*/ */
sjcl.ecc.curve = function(Field, r, a, b, x, y) { sjcl.ecc.curve = function(Field, r, a, b, x, y) {
this.field = Field; this.field = Field;
this.r = Field.prototype.modulus.sub(r); this.r = new sjcl.bn(r);
this.a = new Field(a); this.a = new Field(a);
this.b = new Field(b); this.b = new Field(b);
this.G = new sjcl.ecc.point(this, new Field(x), new Field(y)); this.G = new sjcl.ecc.point(this, new Field(x), new Field(y));
@@ -269,7 +274,7 @@ sjcl.ecc.curve.prototype.fromBits = function (bits) {
sjcl.ecc.curves = { sjcl.ecc.curves = {
c192: new sjcl.ecc.curve( c192: new sjcl.ecc.curve(
sjcl.bn.prime.p192, sjcl.bn.prime.p192,
"0x662107c8eb94364e4b2dd7ce", "0xffffffffffffffffffffffff99def836146bc9b1b4d22831",
-3, -3,
"0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", "0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1",
"0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012", "0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012",
@@ -277,7 +282,7 @@ sjcl.ecc.curves = {
c224: new sjcl.ecc.curve( c224: new sjcl.ecc.curve(
sjcl.bn.prime.p224, sjcl.bn.prime.p224,
"0xe95c1f470fc1ec22d6baa3a3d5c4", "0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d",
-3, -3,
"0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4", "0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4",
"0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21", "0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21",
@@ -285,7 +290,7 @@ sjcl.ecc.curves = {
c256: new sjcl.ecc.curve( c256: new sjcl.ecc.curve(
sjcl.bn.prime.p256, sjcl.bn.prime.p256,
"0x4319055358e8617b0c46353d039cdaae", "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551",
-3, -3,
"0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", "0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b",
"0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", "0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296",
@@ -293,71 +298,135 @@ sjcl.ecc.curves = {
c384: new sjcl.ecc.curve( c384: new sjcl.ecc.curve(
sjcl.bn.prime.p384, sjcl.bn.prime.p384,
"0x389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68c", "0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973",
-3, -3,
"0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef", "0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef",
"0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7", "0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7",
"0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f") "0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f"),
k192: new sjcl.ecc.curve(
sjcl.bn.prime.p192k,
"0xfffffffffffffffffffffffe26f2fc170f69466a74defd8d",
0,
3,
"0xdb4ff10ec057e9ae26b07d0280b7f4341da5d1b1eae06c7d",
"0x9b2f2f6d9c5628a7844163d015be86344082aa88d95e2f9d"),
k224: new sjcl.ecc.curve(
sjcl.bn.prime.p224k,
"0x010000000000000000000000000001dce8d2ec6184caf0a971769fb1f7",
0,
5,
"0xa1455b334df099df30fc28a169a467e9e47075a90f7e650eb6b7a45c",
"0x7e089fed7fba344282cafbd6f7e319f7c0b0bd59e2ca4bdb556d61a5"),
k256: new sjcl.ecc.curve(
sjcl.bn.prime.p256k,
"0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141",
0,
7,
"0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8")
}; };
/** our basicKey classes
/* Diffie-Hellman-like public-key system */ */
sjcl.ecc._dh = function(cn) { sjcl.ecc.basicKey = {
sjcl.ecc[cn] = { /** ecc publicKey.
/** @constructor */ * @constructor
publicKey: function(curve, point) { * @param {curve} curve the elliptic curve
this._curve = curve; * @param {point} point the point on the curve
this._curveBitLength = curve.r.bitLength(); */
if (point instanceof Array) { publicKey: function(curve, point) {
this._point = curve.fromBits(point); this._curve = curve;
} else { this._curveBitLength = curve.r.bitLength();
this._point = point; if (point instanceof Array) {
} this._point = curve.fromBits(point);
} else {
this.get = function() { this._point = point;
var pointbits = this._point.toBits();
var len = sjcl.bitArray.bitLength(pointbits);
var x = sjcl.bitArray.bitSlice(pointbits, 0, len/2);
var y = sjcl.bitArray.bitSlice(pointbits, len/2);
return { x: x, y: y };
}
},
/** @constructor */
secretKey: function(curve, exponent) {
this._curve = curve;
this._curveBitLength = curve.r.bitLength();
this._exponent = exponent;
this.get = function() {
return this._exponent.toBits();
}
},
/** @constructor */
generateKeys: function(curve, paranoia, sec) {
if (curve === undefined) {
curve = 256;
}
if (typeof curve === "number") {
curve = sjcl.ecc.curves['c'+curve];
if (curve === undefined) {
throw new sjcl.exception.invalid("no such curve");
}
}
if (sec === undefined) {
var sec = sjcl.bn.random(curve.r, paranoia);
}
var pub = curve.G.mult(sec);
return { pub: new sjcl.ecc[cn].publicKey(curve, pub),
sec: new sjcl.ecc[cn].secretKey(curve, sec) };
} }
};
/** get this keys point data
* @return x and y as bitArrays
*/
this.get = function() {
var pointbits = this._point.toBits();
var len = sjcl.bitArray.bitLength(pointbits);
var x = sjcl.bitArray.bitSlice(pointbits, 0, len/2);
var y = sjcl.bitArray.bitSlice(pointbits, len/2);
return { x: x, y: y };
};
},
/** ecc secretKey
* @constructor
* @param {curve} curve the elliptic curve
* @param exponent
*/
secretKey: function(curve, exponent) {
this._curve = curve;
this._curveBitLength = curve.r.bitLength();
this._exponent = exponent;
/** get this keys exponent data
* @return {bitArray} exponent
*/
this.get = function () {
return this._exponent.toBits();
};
}
}; };
sjcl.ecc._dh("elGamal"); /** @private */
sjcl.ecc.basicKey.generateKeys = function(cn) {
return function generateKeys(curve, paranoia, sec) {
curve = curve || 256;
if (typeof curve === "number") {
curve = sjcl.ecc.curves['c'+curve];
if (curve === undefined) {
throw new sjcl.exception.invalid("no such curve");
}
}
sec = sec || sjcl.bn.random(curve.r, paranoia);
var pub = curve.G.mult(sec);
return { pub: new sjcl.ecc[cn].publicKey(curve, pub),
sec: new sjcl.ecc[cn].secretKey(curve, sec) };
};
};
/** elGamal keys */
sjcl.ecc.elGamal = {
/** generate keys
* @function
* @param curve
* @param {int} paranoia Paranoia for generation (default 6)
* @param {secretKey} sec secret Key to use. used to get the publicKey for ones secretKey
*/
generateKeys: sjcl.ecc.basicKey.generateKeys("elGamal"),
/** elGamal publicKey.
* @constructor
* @augments sjcl.ecc.basicKey.publicKey
*/
publicKey: function (curve, point) {
sjcl.ecc.basicKey.publicKey.apply(this, arguments);
},
/** elGamal secretKey
* @constructor
* @augments sjcl.ecc.basicKey.secretKey
*/
secretKey: function (curve, exponent) {
sjcl.ecc.basicKey.secretKey.apply(this, arguments);
}
};
sjcl.ecc.elGamal.publicKey.prototype = { sjcl.ecc.elGamal.publicKey.prototype = {
/** Kem function of elGamal Public Key
* @param paranoia paranoia to use for randomization.
* @return {object} key and tag. unkem(tag) with the corresponding secret key results in the key returned.
*/
kem: function(paranoia) { kem: function(paranoia) {
var sec = sjcl.bn.random(this._curve.r, paranoia), var sec = sjcl.bn.random(this._curve.r, paranoia),
tag = this._curve.G.mult(sec).toBits(), tag = this._curve.G.mult(sec).toBits(),
@@ -367,34 +436,58 @@ sjcl.ecc.elGamal.publicKey.prototype = {
}; };
sjcl.ecc.elGamal.secretKey.prototype = { sjcl.ecc.elGamal.secretKey.prototype = {
/** UnKem function of elGamal Secret Key
* @param {bitArray} tag The Tag to decrypt.
* @return {bitArray} decrypted key.
*/
unkem: function(tag) { unkem: function(tag) {
return sjcl.hash.sha256.hash(this._curve.fromBits(tag).mult(this._exponent).toBits()); return sjcl.hash.sha256.hash(this._curve.fromBits(tag).mult(this._exponent).toBits());
}, },
/** Diffie-Hellmann function
* @param {elGamal.publicKey} pk The Public Key to do Diffie-Hellmann with
* @return {bitArray} diffie-hellmann result for this key combination.
*/
dh: function(pk) { dh: function(pk) {
return sjcl.hash.sha256.hash(pk._point.mult(this._exponent).toBits()); return sjcl.hash.sha256.hash(pk._point.mult(this._exponent).toBits());
} },
/** Diffie-Hellmann function, compatible with Java generateSecret
* @param {elGamal.publicKey} pk The Public Key to do Diffie-Hellmann with
* @return {bitArray} undigested X value, diffie-hellmann result for this key combination,
* compatible with Java generateSecret().
*/
dhJavaEc: function(pk) {
return pk._point.mult(this._exponent).x.toBits();
}
}; };
sjcl.ecc._dh("ecdsa"); /** ecdsa keys */
sjcl.ecc.ecdsa = {
sjcl.ecc.ecdsa.secretKey.prototype = { /** generate keys
sign: function(hash, paranoia, fakeLegacyVersion, fixedKForTesting) { * @function
if (sjcl.bitArray.bitLength(hash) > this._curveBitLength) { * @param curve
hash = sjcl.bitArray.clamp(hash, this._curveBitLength); * @param {int} paranoia Paranoia for generation (default 6)
} * @param {secretKey} sec secret Key to use. used to get the publicKey for ones secretKey
var R = this._curve.r, */
l = R.bitLength(), generateKeys: sjcl.ecc.basicKey.generateKeys("ecdsa")
k = fixedKForTesting || sjcl.bn.random(R.sub(1), paranoia).add(1),
r = this._curve.G.mult(k).x.mod(R),
ss = sjcl.bn.fromBits(hash).add(r.mul(this._exponent)),
s = fakeLegacyVersion ? ss.inverseMod(R).mul(k).mod(R)
: ss.mul(k.inverseMod(R)).mod(R);
return sjcl.bitArray.concat(r.toBits(l), s.toBits(l));
}
}; };
/** ecdsa publicKey.
* @constructor
* @augments sjcl.ecc.basicKey.publicKey
*/
sjcl.ecc.ecdsa.publicKey = function (curve, point) {
sjcl.ecc.basicKey.publicKey.apply(this, arguments);
};
/** specific functions for ecdsa publicKey. */
sjcl.ecc.ecdsa.publicKey.prototype = { sjcl.ecc.ecdsa.publicKey.prototype = {
/** Diffie-Hellmann function
* @param {bitArray} hash hash to verify.
* @param {bitArray} rs signature bitArray.
* @param {boolean} fakeLegacyVersion use old legacy version
*/
verify: function(hash, rs, fakeLegacyVersion) { verify: function(hash, rs, fakeLegacyVersion) {
if (sjcl.bitArray.bitLength(hash) > this._curveBitLength) { if (sjcl.bitArray.bitLength(hash) > this._curveBitLength) {
hash = sjcl.bitArray.clamp(hash, this._curveBitLength); hash = sjcl.bitArray.clamp(hash, this._curveBitLength);
@@ -418,3 +511,33 @@ sjcl.ecc.ecdsa.publicKey.prototype = {
return true; return true;
} }
}; };
/** ecdsa secretKey
* @constructor
* @augments sjcl.ecc.basicKey.publicKey
*/
sjcl.ecc.ecdsa.secretKey = function (curve, exponent) {
sjcl.ecc.basicKey.secretKey.apply(this, arguments);
};
/** specific functions for ecdsa secretKey. */
sjcl.ecc.ecdsa.secretKey.prototype = {
/** Diffie-Hellmann function
* @param {bitArray} hash hash to sign.
* @param {int} paranoia paranoia for random number generation
* @param {boolean} fakeLegacyVersion use old legacy version
*/
sign: function(hash, paranoia, fakeLegacyVersion, fixedKForTesting) {
if (sjcl.bitArray.bitLength(hash) > this._curveBitLength) {
hash = sjcl.bitArray.clamp(hash, this._curveBitLength);
}
var R = this._curve.r,
l = R.bitLength(),
k = fixedKForTesting || sjcl.bn.random(R.sub(1), paranoia).add(1),
r = this._curve.G.mult(k).x.mod(R),
ss = sjcl.bn.fromBits(hash).add(r.mul(this._exponent)),
s = fakeLegacyVersion ? ss.inverseMod(R).mul(k).mod(R)
: ss.mul(k.inverseMod(R)).mod(R);
return sjcl.bitArray.concat(r.toBits(l), s.toBits(l));
}
};

View File

@@ -120,7 +120,7 @@ sjcl.mode.gcm = {
* @param {Number} tlen The length of the tag, in bits. * @param {Number} tlen The length of the tag, in bits.
*/ */
_ctrMode: function(encrypt, prf, data, adata, iv, tlen) { _ctrMode: function(encrypt, prf, data, adata, iv, tlen) {
var H, J0, S0, enc, i, ctr, tag, last, l, bl, abl, ivbl, w=sjcl.bitArray, xor=w._xor4; var H, J0, S0, enc, i, ctr, tag, last, l, bl, abl, ivbl, w=sjcl.bitArray;
// Calculate data lengths // Calculate data lengths
l = data.length; l = data.length;

View File

@@ -27,13 +27,35 @@ sjcl.misc.hmac = function (key, Hash) {
this._baseHash[0].update(exKey[0]); this._baseHash[0].update(exKey[0]);
this._baseHash[1].update(exKey[1]); this._baseHash[1].update(exKey[1]);
this._resultHash = new Hash(this._baseHash[0]);
}; };
/** HMAC with the specified hash function. Also called encrypt since it's a prf. /** HMAC with the specified hash function. Also called encrypt since it's a prf.
* @param {bitArray|String} data The data to mac. * @param {bitArray|String} data The data to mac.
*/ */
sjcl.misc.hmac.prototype.encrypt = sjcl.misc.hmac.prototype.mac = function (data) { sjcl.misc.hmac.prototype.encrypt = sjcl.misc.hmac.prototype.mac = function (data) {
var w = new (this._hash)(this._baseHash[0]).update(data).finalize(); if (!this._updated) {
return new (this._hash)(this._baseHash[1]).update(w).finalize(); this.update(data);
return this.digest(data);
} else {
throw new sjcl.exception.invalid("encrypt on already updated hmac called!");
}
}; };
sjcl.misc.hmac.prototype.reset = function () {
this._resultHash = new this._hash(this._baseHash[0]);
this._updated = false;
};
sjcl.misc.hmac.prototype.update = function (data) {
this._updated = true;
this._resultHash.update(data);
};
sjcl.misc.hmac.prototype.digest = function () {
var w = this._resultHash.finalize(), result = new (this._hash)(this._baseHash[1]).update(w).finalize();
this.reset();
return result;
};

View File

@@ -12,7 +12,7 @@
* This is the method specified by RSA's PKCS #5 standard. * This is the method specified by RSA's PKCS #5 standard.
* *
* @param {bitArray|String} password The password. * @param {bitArray|String} password The password.
* @param {bitArray} salt The salt. Should have lots of entropy. * @param {bitArray|String} salt The salt. Should have lots of entropy.
* @param {Number} [count=1000] The number of iterations. Higher numbers make the function slower but more secure. * @param {Number} [count=1000] The number of iterations. Higher numbers make the function slower but more secure.
* @param {Number} [length] The length of the derived key. Defaults to the * @param {Number} [length] The length of the derived key. Defaults to the
output size of the hash function. output size of the hash function.
@@ -30,6 +30,10 @@ sjcl.misc.pbkdf2 = function (password, salt, count, length, Prff) {
password = sjcl.codec.utf8String.toBits(password); password = sjcl.codec.utf8String.toBits(password);
} }
if (typeof salt === "string") {
salt = sjcl.codec.utf8String.toBits(salt);
}
Prff = Prff || sjcl.misc.hmac; Prff = Prff || sjcl.misc.hmac;
var prf = new Prff(password), var prf = new Prff(password),

View File

@@ -3,12 +3,13 @@
* @author Emily Stark * @author Emily Stark
* @author Mike Hamburg * @author Mike Hamburg
* @author Dan Boneh * @author Dan Boneh
* @author Michael Brooks
*/ */
/** @constructor /** @constructor
* @class Random number generator * @class Random number generator
*
* @description * @description
* <b>Use sjcl.random as a singleton for this class!</b>
* <p> * <p>
* This random number generator is a derivative of Ferguson and Schneier's * This random number generator is a derivative of Ferguson and Schneier's
* generator Fortuna. It collects entropy from various events into several * generator Fortuna. It collects entropy from various events into several
@@ -74,10 +75,11 @@ sjcl.prng = function(defaultParanoia) {
this._PARANOIA_LEVELS = [0,48,64,96,128,192,256,384,512,768,1024]; this._PARANOIA_LEVELS = [0,48,64,96,128,192,256,384,512,768,1024];
this._MILLISECONDS_PER_RESEED = 30000; this._MILLISECONDS_PER_RESEED = 30000;
this._BITS_PER_RESEED = 80; this._BITS_PER_RESEED = 80;
} };
sjcl.prng.prototype = { sjcl.prng.prototype = {
/** Generate several random words, and return them in an array /** Generate several random words, and return them in an array.
* A word consists of 32 bits (4 bytes)
* @param {Number} nwords The number of words to generate. * @param {Number} nwords The number of words to generate.
*/ */
randomWords: function (nwords, paranoia) { randomWords: function (nwords, paranoia) {
@@ -102,7 +104,11 @@ sjcl.prng.prototype = {
return out.slice(0,nwords); return out.slice(0,nwords);
}, },
setDefaultParanoia: function (paranoia) { setDefaultParanoia: function (paranoia, allowZeroParanoia) {
if (paranoia === 0 && allowZeroParanoia !== "Setting paranoia=0 will ruin your security; use it only for testing") {
throw "Setting paranoia=0 will ruin your security; use it only for testing";
}
this._defaultParanoia = paranoia; this._defaultParanoia = paranoia;
}, },
@@ -119,7 +125,7 @@ sjcl.prng.prototype = {
i, tmp, i, tmp,
t = (new Date()).valueOf(), t = (new Date()).valueOf(),
robin = this._robins[source], robin = this._robins[source],
oldReady = this.isReady(), err = 0; oldReady = this.isReady(), err = 0, objName;
id = this._collectorIds[source]; id = this._collectorIds[source];
if (id === undefined) { id = this._collectorIds[source] = this._collectorIdNext ++; } if (id === undefined) { id = this._collectorIds[source] = this._collectorIdNext ++; }
@@ -137,7 +143,7 @@ sjcl.prng.prototype = {
break; break;
case "object": case "object":
var objName = Object.prototype.toString.call(data); objName = Object.prototype.toString.call(data);
if (objName === "[object Uint32Array]") { if (objName === "[object Uint32Array]") {
tmp = []; tmp = [];
for (i = 0; i < data.length; i++) { for (i = 0; i < data.length; i++) {
@@ -149,7 +155,7 @@ sjcl.prng.prototype = {
err = 1; err = 1;
} }
for (i=0; i<data.length && !err; i++) { for (i=0; i<data.length && !err; i++) {
if (typeof(data[i]) != "number") { if (typeof(data[i]) !== "number") {
err = 1; err = 1;
} }
} }
@@ -234,14 +240,25 @@ sjcl.prng.prototype = {
startCollectors: function () { startCollectors: function () {
if (this._collectorsStarted) { return; } if (this._collectorsStarted) { return; }
this._eventListener = {
loadTimeCollector: this._bind(this._loadTimeCollector),
mouseCollector: this._bind(this._mouseCollector),
keyboardCollector: this._bind(this._keyboardCollector),
accelerometerCollector: this._bind(this._accelerometerCollector),
touchCollector: this._bind(this._touchCollector)
};
if (window.addEventListener) { if (window.addEventListener) {
window.addEventListener("load", this._loadTimeCollector, false); window.addEventListener("load", this._eventListener.loadTimeCollector, false);
window.addEventListener("mousemove", this._mouseCollector, false); window.addEventListener("mousemove", this._eventListener.mouseCollector, false);
window.addEventListener("keypress", this._eventListener.keyboardCollector, false);
window.addEventListener("devicemotion", this._eventListener.accelerometerCollector, false);
window.addEventListener("touchmove", this._eventListener.touchCollector, false);
} else if (document.attachEvent) { } else if (document.attachEvent) {
document.attachEvent("onload", this._loadTimeCollector); document.attachEvent("onload", this._eventListener.loadTimeCollector);
document.attachEvent("onmousemove", this._mouseCollector); document.attachEvent("onmousemove", this._eventListener.mouseCollector);
} document.attachEvent("keypress", this._eventListener.keyboardCollector);
else { } else {
throw new sjcl.exception.bug("can't attach event"); throw new sjcl.exception.bug("can't attach event");
} }
@@ -253,12 +270,17 @@ sjcl.prng.prototype = {
if (!this._collectorsStarted) { return; } if (!this._collectorsStarted) { return; }
if (window.removeEventListener) { if (window.removeEventListener) {
window.removeEventListener("load", this._loadTimeCollector, false); window.removeEventListener("load", this._eventListener.loadTimeCollector, false);
window.removeEventListener("mousemove", this._mouseCollector, false); window.removeEventListener("mousemove", this._eventListener.mouseCollector, false);
} else if (window.detachEvent) { window.removeEventListener("keypress", this._eventListener.keyboardCollector, false);
window.detachEvent("onload", this._loadTimeCollector); window.removeEventListener("devicemotion", this._eventListener.accelerometerCollector, false);
window.detachEvent("onmousemove", this._mouseCollector); window.removeEventListener("touchmove", this._eventListener.touchCollector, false);
} else if (document.detachEvent) {
document.detachEvent("onload", this._eventListener.loadTimeCollector);
document.detachEvent("onmousemove", this._eventListener.mouseCollector);
document.detachEvent("keypress", this._eventListener.keyboardCollector);
} }
this._collectorsStarted = false; this._collectorsStarted = false;
}, },
@@ -275,23 +297,30 @@ sjcl.prng.prototype = {
/** remove an event listener for progress or seeded-ness */ /** remove an event listener for progress or seeded-ness */
removeEventListener: function (name, cb) { removeEventListener: function (name, cb) {
var i, j, cbs=this._callbacks[name], jsTemp=[]; var i, j, cbs=this._callbacks[name], jsTemp=[];
/* I'm not sure if this is necessary; in C++, iterating over a /* I'm not sure if this is necessary; in C++, iterating over a
* collection and modifying it at the same time is a no-no. * collection and modifying it at the same time is a no-no.
*/ */
for (j in cbs) { for (j in cbs) {
if (cbs.hasOwnProperty(j) && cbs[j] === cb) { if (cbs.hasOwnProperty(j) && cbs[j] === cb) {
jsTemp.push(j); jsTemp.push(j);
} }
} }
for (i=0; i<jsTemp.length; i++) { for (i=0; i<jsTemp.length; i++) {
j = jsTemp[i]; j = jsTemp[i];
delete cbs[j]; delete cbs[j];
} }
}, },
_bind: function (func) {
var that = this;
return function () {
func.apply(that, arguments);
};
},
/** Generate 4 random words, no reseed, no gate. /** Generate 4 random words, no reseed, no gate.
* @private * @private
*/ */
@@ -363,44 +392,131 @@ sjcl.prng.prototype = {
this._reseed(reseedData); this._reseed(reseedData);
}, },
_keyboardCollector: function () {
this._addCurrentTimeToEntropy(1);
},
_mouseCollector: function (ev) { _mouseCollector: function (ev) {
var x = ev.x || ev.clientX || ev.offsetX || 0, y = ev.y || ev.clientY || ev.offsetY || 0; var x, y;
sjcl.random.addEntropy([x,y], 2, "mouse");
try {
x = ev.x || ev.clientX || ev.offsetX || 0;
y = ev.y || ev.clientY || ev.offsetY || 0;
} catch (err) {
// Event originated from a secure element. No mouse position available.
x = 0;
y = 0;
}
if (x != 0 && y!= 0) {
sjcl.random.addEntropy([x,y], 2, "mouse");
}
this._addCurrentTimeToEntropy(0);
},
_touchCollector: function(ev) {
var touch = ev.touches[0] || ev.changedTouches[0];
var x = touch.pageX || touch.clientX,
y = touch.pageY || touch.clientY;
sjcl.random.addEntropy([x,y],1,"touch");
this._addCurrentTimeToEntropy(0);
}, },
_loadTimeCollector: function (ev) { _loadTimeCollector: function () {
sjcl.random.addEntropy((new Date()).valueOf(), 2, "loadtime"); this._addCurrentTimeToEntropy(2);
}, },
_addCurrentTimeToEntropy: function (estimatedEntropy) {
if (typeof window !== 'undefined' && window.performance && typeof window.performance.now === "function") {
//how much entropy do we want to add here?
sjcl.random.addEntropy(window.performance.now(), estimatedEntropy, "loadtime");
} else {
sjcl.random.addEntropy((new Date()).valueOf(), estimatedEntropy, "loadtime");
}
},
_accelerometerCollector: function (ev) {
var ac = ev.accelerationIncludingGravity.x||ev.accelerationIncludingGravity.y||ev.accelerationIncludingGravity.z;
if(window.orientation){
var or = window.orientation;
if (typeof or === "number") {
sjcl.random.addEntropy(or, 1, "accelerometer");
}
}
if (ac) {
sjcl.random.addEntropy(ac, 2, "accelerometer");
}
this._addCurrentTimeToEntropy(0);
},
_fireEvent: function (name, arg) { _fireEvent: function (name, arg) {
var j, cbs=sjcl.random._callbacks[name], cbsTemp=[]; var j, cbs=sjcl.random._callbacks[name], cbsTemp=[];
/* TODO: there is a race condition between removing collectors and firing them */ /* TODO: there is a race condition between removing collectors and firing them */
/* I'm not sure if this is necessary; in C++, iterating over a /* I'm not sure if this is necessary; in C++, iterating over a
* collection and modifying it at the same time is a no-no. * collection and modifying it at the same time is a no-no.
*/ */
for (j in cbs) { for (j in cbs) {
if (cbs.hasOwnProperty(j)) { if (cbs.hasOwnProperty(j)) {
cbsTemp.push(cbs[j]); cbsTemp.push(cbs[j]);
} }
} }
for (j=0; j<cbsTemp.length; j++) { for (j=0; j<cbsTemp.length; j++) {
cbsTemp[j](arg); cbsTemp[j](arg);
} }
} }
}; };
/** an instance for the prng.
* @see sjcl.prng
*/
sjcl.random = new sjcl.prng(6); sjcl.random = new sjcl.prng(6);
(function(){ (function(){
try { // function for getting nodejs crypto module. catches and ignores errors.
// get cryptographically strong entropy in Webkit function getCryptoModule() {
var ab = new Uint32Array(32); try {
crypto.getRandomValues(ab); return require('crypto');
sjcl.random.addEntropy(ab, 1024, "crypto.getRandomValues"); }
} catch (e) { catch (e) {
// no getRandomValues :-( return null;
}
} }
})();
try {
var buf, crypt, ab;
// get cryptographically strong entropy depending on runtime environment
if (typeof module !== 'undefined' && module.exports && (crypt = getCryptoModule()) && crypt.randomBytes) {
buf = crypt.randomBytes(1024/8);
buf = new Uint32Array(new Uint8Array(buf).buffer);
sjcl.random.addEntropy(buf, 1024, "crypto.randomBytes");
} else if (typeof window !== 'undefined' && typeof Uint32Array !== 'undefined') {
ab = new Uint32Array(32);
if (window.crypto && window.crypto.getRandomValues) {
window.crypto.getRandomValues(ab);
} else if (window.msCrypto && window.msCrypto.getRandomValues) {
window.msCrypto.getRandomValues(ab);
} else {
return;
}
// get cryptographically strong entropy in Webkit
sjcl.random.addEntropy(ab, 1024, "crypto.getRandomValues");
} else {
// no getRandomValues :-(
}
} catch (e) {
if (typeof window !== 'undefined' && window.console) {
console.log("There was an error collecting entropy from the browser:");
console.log(e);
//we do not want the library to fail due to randomness not being maintained.
}
}
}());

View File

@@ -138,8 +138,7 @@ sjcl.hash.sha1.prototype = {
_block:function (words) { _block:function (words) {
var t, tmp, a, b, c, d, e, var t, tmp, a, b, c, d, e,
w = words.slice(0), w = words.slice(0),
h = this._h, h = this._h;
k = this._key;
a = h[0]; b = h[1]; c = h[2]; d = h[3]; e = h[4]; a = h[0]; b = h[1]; c = h[2]; d = h[3]; e = h[4];

View File

@@ -299,7 +299,7 @@ sjcl.hash.sha512.prototype = {
t1h += chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0); t1h += chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0);
t1l += krl; t1l += krl;
t1h += krh + ((t1l >>> 0) < (krl >>> 0) ? 1 : 0); t1h += krh + ((t1l >>> 0) < (krl >>> 0) ? 1 : 0);
t1l += wrl; t1l = t1l + wrl|0; // FF32..FF34 perf issue https://bugzilla.mozilla.org/show_bug.cgi?id=1054972
t1h += wrh + ((t1l >>> 0) < (wrl >>> 0) ? 1 : 0); t1h += wrh + ((t1l >>> 0) < (wrl >>> 0) ? 1 : 0);
// t2 = sigma0 + maj // t2 = sigma0 + maj

View File

@@ -10,7 +10,7 @@
"use strict"; "use strict";
/*jslint indent: 2, bitwise: false, nomen: false, plusplus: false, white: false, regexp: false */ /*jslint indent: 2, bitwise: false, nomen: false, plusplus: false, white: false, regexp: false */
/*global document, window, escape, unescape */ /*global document, window, escape, unescape, module, require, Uint32Array */
/** @namespace The Stanford Javascript Crypto Library, top-level namespace. */ /** @namespace The Stanford Javascript Crypto Library, top-level namespace. */
var sjcl = { var sjcl = {
@@ -68,6 +68,11 @@ var sjcl = {
} }
}; };
if(typeof module != 'undefined' && module.exports){ if(typeof module !== 'undefined' && module.exports){
module.exports = sjcl; module.exports = sjcl;
} }
if (typeof define === "function") {
define([], function () {
return sjcl;
});
}

View File

@@ -36,7 +36,7 @@
<h2>Key Derivation</h2> <h2>Key Derivation</h2>
<div class="section"> <div class="section">
<div> <div>
<label for="salt"">Salt:</label> <label for="salt">Salt:</label>
<a class="random floatright" href="javascript:randomize('salt',2,0)">random</a> <a class="random floatright" href="javascript:randomize('salt',2,0)">random</a>
</div> </div>
<input type="text" id="salt" class="wide hex" autocomplete="off" size="17" maxlength="35"/> <input type="text" id="salt" class="wide hex" autocomplete="off" size="17" maxlength="35"/>

16
src/js/sjcl/package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "sjcl",
"version": "1.0.1",
"description": "Stanford Javascript Crypto Library",
"main": "sjcl.js",
"author": "bitwiseshiftleft",
"keywords": ["encryption", "high-level", "crypto"],
"repository" : {
"type": "git",
"url": "https://github.com/bitwiseshiftleft/sjcl.git"
},
"scripts": {
"test": "make test"
},
"engines": { "node": "*" }
}

View File

@@ -1,48 +1,54 @@
"use strict";function q(a){throw a;}var t=void 0,u=!1;var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}}; "use strict";function q(a){throw a;}var t=void 0,u=!1;var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}};
"undefined"!=typeof module&&module.exports&&(module.exports=sjcl); "undefined"!==typeof module&&module.exports&&(module.exports=sjcl);"function"===typeof define&&define([],function(){return sjcl});
sjcl.cipher.aes=function(a){this.j[0][0][0]||this.D();var b,c,d,e,f=this.j[0][4],g=this.j[1];b=a.length;var h=1;4!==b&&(6!==b&&8!==b)&&q(new sjcl.exception.invalid("invalid aes key size"));this.a=[d=a.slice(0),e=[]];for(a=b;a<4*b+28;a++){c=d[a-1];if(0===a%b||8===b&&4===a%b)c=f[c>>>24]<<24^f[c>>16&255]<<16^f[c>>8&255]<<8^f[c&255],0===a%b&&(c=c<<8^c>>>24^h<<24,h=h<<1^283*(h>>7));d[a]=d[a-b]^c}for(b=0;a;b++,a--)c=d[b&3?a:a-4],e[b]=4>=a||4>b?c:g[0][f[c>>>24]]^g[1][f[c>>16&255]]^g[2][f[c>>8&255]]^g[3][f[c& sjcl.cipher.aes=function(a){this.k[0][0][0]||this.D();var b,c,d,e,f=this.k[0][4],g=this.k[1];b=a.length;var h=1;4!==b&&(6!==b&&8!==b)&&q(new sjcl.exception.invalid("invalid aes key size"));this.b=[d=a.slice(0),e=[]];for(a=b;a<4*b+28;a++){c=d[a-1];if(0===a%b||8===b&&4===a%b)c=f[c>>>24]<<24^f[c>>16&255]<<16^f[c>>8&255]<<8^f[c&255],0===a%b&&(c=c<<8^c>>>24^h<<24,h=h<<1^283*(h>>7));d[a]=d[a-b]^c}for(b=0;a;b++,a--)c=d[b&3?a:a-4],e[b]=4>=a||4>b?c:g[0][f[c>>>24]]^g[1][f[c>>16&255]]^g[2][f[c>>8&255]]^g[3][f[c&
255]]}; 255]]};
sjcl.cipher.aes.prototype={encrypt:function(a){return y(this,a,0)},decrypt:function(a){return y(this,a,1)},j:[[[],[],[],[],[]],[[],[],[],[],[]]],D:function(){var a=this.j[0],b=this.j[1],c=a[4],d=b[4],e,f,g,h=[],l=[],k,n,m,p;for(e=0;0x100>e;e++)l[(h[e]=e<<1^283*(e>>7))^e]=e;for(f=g=0;!c[f];f^=k||1,g=l[g]||1){m=g^g<<1^g<<2^g<<3^g<<4;m=m>>8^m&255^99;c[f]=m;d[m]=f;n=h[e=h[k=h[f]]];p=0x1010101*n^0x10001*e^0x101*k^0x1010100*f;n=0x101*h[m]^0x1010100*m;for(e=0;4>e;e++)a[e][f]=n=n<<24^n>>>8,b[e][m]=p=p<<24^p>>>8}for(e= sjcl.cipher.aes.prototype={encrypt:function(a){return y(this,a,0)},decrypt:function(a){return y(this,a,1)},k:[[[],[],[],[],[]],[[],[],[],[],[]]],D:function(){var a=this.k[0],b=this.k[1],c=a[4],d=b[4],e,f,g,h=[],l=[],k,n,m,p;for(e=0;0x100>e;e++)l[(h[e]=e<<1^283*(e>>7))^e]=e;for(f=g=0;!c[f];f^=k||1,g=l[g]||1){m=g^g<<1^g<<2^g<<3^g<<4;m=m>>8^m&255^99;c[f]=m;d[m]=f;n=h[e=h[k=h[f]]];p=0x1010101*n^0x10001*e^0x101*k^0x1010100*f;n=0x101*h[m]^0x1010100*m;for(e=0;4>e;e++)a[e][f]=n=n<<24^n>>>8,b[e][m]=p=p<<24^p>>>8}for(e=
0;5>e;e++)a[e]=a[e].slice(0),b[e]=b[e].slice(0)}}; 0;5>e;e++)a[e]=a[e].slice(0),b[e]=b[e].slice(0)}};
function y(a,b,c){4!==b.length&&q(new sjcl.exception.invalid("invalid aes block size"));var d=a.a[c],e=b[0]^d[0],f=b[c?3:1]^d[1],g=b[2]^d[2];b=b[c?1:3]^d[3];var h,l,k,n=d.length/4-2,m,p=4,s=[0,0,0,0];h=a.j[c];a=h[0];var r=h[1],v=h[2],w=h[3],x=h[4];for(m=0;m<n;m++)h=a[e>>>24]^r[f>>16&255]^v[g>>8&255]^w[b&255]^d[p],l=a[f>>>24]^r[g>>16&255]^v[b>>8&255]^w[e&255]^d[p+1],k=a[g>>>24]^r[b>>16&255]^v[e>>8&255]^w[f&255]^d[p+2],b=a[b>>>24]^r[e>>16&255]^v[f>>8&255]^w[g&255]^d[p+3],p+=4,e=h,f=l,g=k;for(m=0;4> function y(a,b,c){4!==b.length&&q(new sjcl.exception.invalid("invalid aes block size"));var d=a.b[c],e=b[0]^d[0],f=b[c?3:1]^d[1],g=b[2]^d[2];b=b[c?1:3]^d[3];var h,l,k,n=d.length/4-2,m,p=4,s=[0,0,0,0];h=a.k[c];a=h[0];var r=h[1],v=h[2],w=h[3],x=h[4];for(m=0;m<n;m++)h=a[e>>>24]^r[f>>16&255]^v[g>>8&255]^w[b&255]^d[p],l=a[f>>>24]^r[g>>16&255]^v[b>>8&255]^w[e&255]^d[p+1],k=a[g>>>24]^r[b>>16&255]^v[e>>8&255]^w[f&255]^d[p+2],b=a[b>>>24]^r[e>>16&255]^v[f>>8&255]^w[g&255]^d[p+3],p+=4,e=h,f=l,g=k;for(m=0;4>
m;m++)s[c?3&-m:m]=x[e>>>24]<<24^x[f>>16&255]<<16^x[g>>8&255]<<8^x[b&255]^d[p++],h=e,e=f,f=g,g=b,b=h;return s} m;m++)s[c?3&-m:m]=x[e>>>24]<<24^x[f>>16&255]<<16^x[g>>8&255]<<8^x[b&255]^d[p++],h=e,e=f,f=g,g=b,b=h;return s}
sjcl.bitArray={bitSlice:function(a,b,c){a=sjcl.bitArray.O(a.slice(b/32),32-(b&31)).slice(1);return c===t?a:sjcl.bitArray.clamp(a,c-b)},extract:function(a,b,c){var d=Math.floor(-b-c&31);return((b+c-1^b)&-32?a[b/32|0]<<32-d^a[b/32+1|0]>>>d:a[b/32|0]>>>d)&(1<<c)-1},concat:function(a,b){if(0===a.length||0===b.length)return a.concat(b);var c=a[a.length-1],d=sjcl.bitArray.getPartial(c);return 32===d?a.concat(b):sjcl.bitArray.O(b,d,c|0,a.slice(0,a.length-1))},bitLength:function(a){var b=a.length;return 0=== sjcl.bitArray={bitSlice:function(a,b,c){a=sjcl.bitArray.P(a.slice(b/32),32-(b&31)).slice(1);return c===t?a:sjcl.bitArray.clamp(a,c-b)},extract:function(a,b,c){var d=Math.floor(-b-c&31);return((b+c-1^b)&-32?a[b/32|0]<<32-d^a[b/32+1|0]>>>d:a[b/32|0]>>>d)&(1<<c)-1},concat:function(a,b){if(0===a.length||0===b.length)return a.concat(b);var c=a[a.length-1],d=sjcl.bitArray.getPartial(c);return 32===d?a.concat(b):sjcl.bitArray.P(b,d,c|0,a.slice(0,a.length-1))},bitLength:function(a){var b=a.length;return 0===
b?0:32*(b-1)+sjcl.bitArray.getPartial(a[b-1])},clamp:function(a,b){if(32*a.length<b)return a;a=a.slice(0,Math.ceil(b/32));var c=a.length;b&=31;0<c&&b&&(a[c-1]=sjcl.bitArray.partial(b,a[c-1]&2147483648>>b-1,1));return a},partial:function(a,b,c){return 32===a?b:(c?b|0:b<<32-a)+0x10000000000*a},getPartial:function(a){return Math.round(a/0x10000000000)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return u;var c=0,d;for(d=0;d<a.length;d++)c|=a[d]^b[d];return 0=== b?0:32*(b-1)+sjcl.bitArray.getPartial(a[b-1])},clamp:function(a,b){if(32*a.length<b)return a;a=a.slice(0,Math.ceil(b/32));var c=a.length;b&=31;0<c&&b&&(a[c-1]=sjcl.bitArray.partial(b,a[c-1]&2147483648>>b-1,1));return a},partial:function(a,b,c){return 32===a?b:(c?b|0:b<<32-a)+0x10000000000*a},getPartial:function(a){return Math.round(a/0x10000000000)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return u;var c=0,d;for(d=0;d<a.length;d++)c|=a[d]^b[d];return 0===
c},O:function(a,b,c,d){var e;e=0;for(d===t&&(d=[]);32<=b;b-=32)d.push(c),c=0;if(0===b)return d.concat(a);for(e=0;e<a.length;e++)d.push(c|a[e]>>>b),c=a[e]<<32-b;e=a.length?a[a.length-1]:0;a=sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b+a&31,32<b+a?c:d.pop(),1));return d},k:function(a,b){return[a[0]^b[0],a[1]^b[1],a[2]^b[2],a[3]^b[3]]}}; c},P:function(a,b,c,d){var e;e=0;for(d===t&&(d=[]);32<=b;b-=32)d.push(c),c=0;if(0===b)return d.concat(a);for(e=0;e<a.length;e++)d.push(c|a[e]>>>b),c=a[e]<<32-b;e=a.length?a[a.length-1]:0;a=sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b+a&31,32<b+a?c:d.pop(),1));return d},l:function(a,b){return[a[0]^b[0],a[1]^b[1],a[2]^b[2],a[3]^b[3]]},byteswapM:function(a){var b,c;for(b=0;b<a.length;++b)c=a[b],a[b]=c>>>24|c>>>8&0xff00|(c&0xff00)<<8|c<<24;return a}};
sjcl.codec.utf8String={fromBits:function(a){var b="",c=sjcl.bitArray.bitLength(a),d,e;for(d=0;d<c/8;d++)0===(d&3)&&(e=a[d/4]),b+=String.fromCharCode(e>>>24),e<<=8;return decodeURIComponent(escape(b))},toBits:function(a){a=unescape(encodeURIComponent(a));var b=[],c,d=0;for(c=0;c<a.length;c++)d=d<<8|a.charCodeAt(c),3===(c&3)&&(b.push(d),d=0);c&3&&b.push(sjcl.bitArray.partial(8*(c&3),d));return b}}; sjcl.codec.utf8String={fromBits:function(a){var b="",c=sjcl.bitArray.bitLength(a),d,e;for(d=0;d<c/8;d++)0===(d&3)&&(e=a[d/4]),b+=String.fromCharCode(e>>>24),e<<=8;return decodeURIComponent(escape(b))},toBits:function(a){a=unescape(encodeURIComponent(a));var b=[],c,d=0;for(c=0;c<a.length;c++)d=d<<8|a.charCodeAt(c),3===(c&3)&&(b.push(d),d=0);c&3&&b.push(sjcl.bitArray.partial(8*(c&3),d));return b}};
sjcl.codec.hex={fromBits:function(a){var b="",c;for(c=0;c<a.length;c++)b+=((a[c]|0)+0xf00000000000).toString(16).substr(4);return b.substr(0,sjcl.bitArray.bitLength(a)/4)},toBits:function(a){var b,c=[],d;a=a.replace(/\s|0x/g,"");d=a.length;a+="00000000";for(b=0;b<a.length;b+=8)c.push(parseInt(a.substr(b,8),16)^0);return sjcl.bitArray.clamp(c,4*d)}}; sjcl.codec.hex={fromBits:function(a){var b="",c;for(c=0;c<a.length;c++)b+=((a[c]|0)+0xf00000000000).toString(16).substr(4);return b.substr(0,sjcl.bitArray.bitLength(a)/4)},toBits:function(a){var b,c=[],d;a=a.replace(/\s|0x/g,"");d=a.length;a+="00000000";for(b=0;b<a.length;b+=8)c.push(parseInt(a.substr(b,8),16)^0);return sjcl.bitArray.clamp(c,4*d)}};
sjcl.codec.base64={I:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fromBits:function(a,b,c){var d="",e=0,f=sjcl.codec.base64.I,g=0,h=sjcl.bitArray.bitLength(a);c&&(f=f.substr(0,62)+"-_");for(c=0;6*d.length<h;)d+=f.charAt((g^a[c]>>>e)>>>26),6>e?(g=a[c]<<6-e,e+=26,c++):(g<<=6,e-=6);for(;d.length&3&&!b;)d+="=";return d},toBits:function(a,b){a=a.replace(/\s|=/g,"");var c=[],d,e=0,f=sjcl.codec.base64.I,g=0,h;b&&(f=f.substr(0,62)+"-_");for(d=0;d<a.length;d++)h=f.indexOf(a.charAt(d)), sjcl.codec.base64={J:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fromBits:function(a,b,c){var d="",e=0,f=sjcl.codec.base64.J,g=0,h=sjcl.bitArray.bitLength(a);c&&(f=f.substr(0,62)+"-_");for(c=0;6*d.length<h;)d+=f.charAt((g^a[c]>>>e)>>>26),6>e?(g=a[c]<<6-e,e+=26,c++):(g<<=6,e-=6);for(;d.length&3&&!b;)d+="=";return d},toBits:function(a,b){a=a.replace(/\s|=/g,"");var c=[],d,e=0,f=sjcl.codec.base64.J,g=0,h;b&&(f=f.substr(0,62)+"-_");for(d=0;d<a.length;d++)h=f.indexOf(a.charAt(d)),
0>h&&q(new sjcl.exception.invalid("this isn't base64!")),26<e?(e-=26,c.push(g^h>>>e),g=h<<32-e):(e+=6,g^=h<<32-e);e&56&&c.push(sjcl.bitArray.partial(e&56,g,1));return c}};sjcl.codec.base64url={fromBits:function(a){return sjcl.codec.base64.fromBits(a,1,1)},toBits:function(a){return sjcl.codec.base64.toBits(a,1)}};sjcl.hash.sha256=function(a){this.a[0]||this.D();a?(this.q=a.q.slice(0),this.m=a.m.slice(0),this.g=a.g):this.reset()};sjcl.hash.sha256.hash=function(a){return(new sjcl.hash.sha256).update(a).finalize()}; 0>h&&q(new sjcl.exception.invalid("this isn't base64!")),26<e?(e-=26,c.push(g^h>>>e),g=h<<32-e):(e+=6,g^=h<<32-e);e&56&&c.push(sjcl.bitArray.partial(e&56,g,1));return c}};sjcl.codec.base64url={fromBits:function(a){return sjcl.codec.base64.fromBits(a,1,1)},toBits:function(a){return sjcl.codec.base64.toBits(a,1)}};sjcl.hash.sha256=function(a){this.b[0]||this.D();a?(this.r=a.r.slice(0),this.o=a.o.slice(0),this.h=a.h):this.reset()};sjcl.hash.sha256.hash=function(a){return(new sjcl.hash.sha256).update(a).finalize()};
sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.q=this.M.slice(0);this.m=[];this.g=0;return this},update:function(a){"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));var b,c=this.m=sjcl.bitArray.concat(this.m,a);b=this.g;a=this.g=b+sjcl.bitArray.bitLength(a);for(b=512+b&-512;b<=a;b+=512)z(this,c.splice(0,16));return this},finalize:function(){var a,b=this.m,c=this.q,b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0);b.push(Math.floor(this.g/ sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.r=this.N.slice(0);this.o=[];this.h=0;return this},update:function(a){"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));var b,c=this.o=sjcl.bitArray.concat(this.o,a);b=this.h;a=this.h=b+sjcl.bitArray.bitLength(a);for(b=512+b&-512;b<=a;b+=512)z(this,c.splice(0,16));return this},finalize:function(){var a,b=this.o,c=this.r,b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0);b.push(Math.floor(this.h/
4294967296));for(b.push(this.g|0);b.length;)z(this,b.splice(0,16));this.reset();return c},M:[],a:[],D:function(){function a(a){return 0x100000000*(a-Math.floor(a))|0}var b=0,c=2,d;a:for(;64>b;c++){for(d=2;d*d<=c;d++)if(0===c%d)continue a;8>b&&(this.M[b]=a(Math.pow(c,0.5)));this.a[b]=a(Math.pow(c,1/3));b++}}}; 4294967296));for(b.push(this.h|0);b.length;)z(this,b.splice(0,16));this.reset();return c},N:[],b:[],D:function(){function a(a){return 0x100000000*(a-Math.floor(a))|0}var b=0,c=2,d;a:for(;64>b;c++){for(d=2;d*d<=c;d++)if(0===c%d)continue a;8>b&&(this.N[b]=a(Math.pow(c,0.5)));this.b[b]=a(Math.pow(c,1/3));b++}}};
function z(a,b){var c,d,e,f=b.slice(0),g=a.q,h=a.a,l=g[0],k=g[1],n=g[2],m=g[3],p=g[4],s=g[5],r=g[6],v=g[7];for(c=0;64>c;c++)16>c?d=f[c]:(d=f[c+1&15],e=f[c+14&15],d=f[c&15]=(d>>>7^d>>>18^d>>>3^d<<25^d<<14)+(e>>>17^e>>>19^e>>>10^e<<15^e<<13)+f[c&15]+f[c+9&15]|0),d=d+v+(p>>>6^p>>>11^p>>>25^p<<26^p<<21^p<<7)+(r^p&(s^r))+h[c],v=r,r=s,s=p,p=m+d|0,m=n,n=k,k=l,l=d+(k&n^m&(k^n))+(k>>>2^k>>>13^k>>>22^k<<30^k<<19^k<<10)|0;g[0]=g[0]+l|0;g[1]=g[1]+k|0;g[2]=g[2]+n|0;g[3]=g[3]+m|0;g[4]=g[4]+p|0;g[5]=g[5]+s|0;g[6]= function z(a,b){var c,d,e,f=b.slice(0),g=a.r,h=a.b,l=g[0],k=g[1],n=g[2],m=g[3],p=g[4],s=g[5],r=g[6],v=g[7];for(c=0;64>c;c++)16>c?d=f[c]:(d=f[c+1&15],e=f[c+14&15],d=f[c&15]=(d>>>7^d>>>18^d>>>3^d<<25^d<<14)+(e>>>17^e>>>19^e>>>10^e<<15^e<<13)+f[c&15]+f[c+9&15]|0),d=d+v+(p>>>6^p>>>11^p>>>25^p<<26^p<<21^p<<7)+(r^p&(s^r))+h[c],v=r,r=s,s=p,p=m+d|0,m=n,n=k,k=l,l=d+(k&n^m&(k^n))+(k>>>2^k>>>13^k>>>22^k<<30^k<<19^k<<10)|0;g[0]=g[0]+l|0;g[1]=g[1]+k|0;g[2]=g[2]+n|0;g[3]=g[3]+m|0;g[4]=g[4]+p|0;g[5]=g[5]+s|0;g[6]=
g[6]+r|0;g[7]=g[7]+v|0} g[6]+r|0;g[7]=g[7]+v|0}
sjcl.mode.ccm={name:"ccm",encrypt:function(a,b,c,d,e){var f,g=b.slice(0),h=sjcl.bitArray,l=h.bitLength(c)/8,k=h.bitLength(g)/8;e=e||64;d=d||[];7>l&&q(new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"));for(f=2;4>f&&k>>>8*f;f++);f<15-l&&(f=15-l);c=h.clamp(c,8*(15-f));b=sjcl.mode.ccm.K(a,b,c,d,e,f);g=sjcl.mode.ccm.n(a,g,c,b,e,f);return h.concat(g.data,g.tag)},decrypt:function(a,b,c,d,e){e=e||64;d=d||[];var f=sjcl.bitArray,g=f.bitLength(c)/8,h=f.bitLength(b),l=f.clamp(b,h-e),k=f.bitSlice(b, sjcl.mode.ccm={name:"ccm",encrypt:function(a,b,c,d,e){var f,g=b.slice(0),h=sjcl.bitArray,l=h.bitLength(c)/8,k=h.bitLength(g)/8;e=e||64;d=d||[];7>l&&q(new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"));for(f=2;4>f&&k>>>8*f;f++);f<15-l&&(f=15-l);c=h.clamp(c,8*(15-f));b=sjcl.mode.ccm.L(a,b,c,d,e,f);g=sjcl.mode.ccm.p(a,g,c,b,e,f);return h.concat(g.data,g.tag)},decrypt:function(a,b,c,d,e){e=e||64;d=d||[];var f=sjcl.bitArray,g=f.bitLength(c)/8,h=f.bitLength(b),l=f.clamp(b,h-e),k=f.bitSlice(b,
h-e),h=(h-e)/8;7>g&&q(new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"));for(b=2;4>b&&h>>>8*b;b++);b<15-g&&(b=15-g);c=f.clamp(c,8*(15-b));l=sjcl.mode.ccm.n(a,l,c,k,e,b);a=sjcl.mode.ccm.K(a,l.data,c,d,e,b);f.equal(l.tag,a)||q(new sjcl.exception.corrupt("ccm: tag doesn't match"));return l.data},K:function(a,b,c,d,e,f){var g=[],h=sjcl.bitArray,l=h.k;e/=8;(e%2||4>e||16<e)&&q(new sjcl.exception.invalid("ccm: invalid tag length"));(0xffffffff<d.length||0xffffffff<b.length)&&q(new sjcl.exception.bug("ccm: can't deal with 4GiB or more data")); h-e),h=(h-e)/8;7>g&&q(new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"));for(b=2;4>b&&h>>>8*b;b++);b<15-g&&(b=15-g);c=f.clamp(c,8*(15-b));l=sjcl.mode.ccm.p(a,l,c,k,e,b);a=sjcl.mode.ccm.L(a,l.data,c,d,e,b);f.equal(l.tag,a)||q(new sjcl.exception.corrupt("ccm: tag doesn't match"));return l.data},L:function(a,b,c,d,e,f){var g=[],h=sjcl.bitArray,l=h.l;e/=8;(e%2||4>e||16<e)&&q(new sjcl.exception.invalid("ccm: invalid tag length"));(0xffffffff<d.length||0xffffffff<b.length)&&q(new sjcl.exception.bug("ccm: can't deal with 4GiB or more data"));
f=[h.partial(8,(d.length?64:0)|e-2<<2|f-1)];f=h.concat(f,c);f[3]|=h.bitLength(b)/8;f=a.encrypt(f);if(d.length){c=h.bitLength(d)/8;65279>=c?g=[h.partial(16,c)]:0xffffffff>=c&&(g=h.concat([h.partial(16,65534)],[c]));g=h.concat(g,d);for(d=0;d<g.length;d+=4)f=a.encrypt(l(f,g.slice(d,d+4).concat([0,0,0])))}for(d=0;d<b.length;d+=4)f=a.encrypt(l(f,b.slice(d,d+4).concat([0,0,0])));return h.clamp(f,8*e)},n:function(a,b,c,d,e,f){var g,h=sjcl.bitArray;g=h.k;var l=b.length,k=h.bitLength(b);c=h.concat([h.partial(8, f=[h.partial(8,(d.length?64:0)|e-2<<2|f-1)];f=h.concat(f,c);f[3]|=h.bitLength(b)/8;f=a.encrypt(f);if(d.length){c=h.bitLength(d)/8;65279>=c?g=[h.partial(16,c)]:0xffffffff>=c&&(g=h.concat([h.partial(16,65534)],[c]));g=h.concat(g,d);for(d=0;d<g.length;d+=4)f=a.encrypt(l(f,g.slice(d,d+4).concat([0,0,0])))}for(d=0;d<b.length;d+=4)f=a.encrypt(l(f,b.slice(d,d+4).concat([0,0,0])));return h.clamp(f,8*e)},p:function(a,b,c,d,e,f){var g,h=sjcl.bitArray;g=h.l;var l=b.length,k=h.bitLength(b);c=h.concat([h.partial(8,
f-1)],c).concat([0,0,0]).slice(0,4);d=h.bitSlice(g(d,a.encrypt(c)),0,e);if(!l)return{tag:d,data:[]};for(g=0;g<l;g+=4)c[3]++,e=a.encrypt(c),b[g]^=e[0],b[g+1]^=e[1],b[g+2]^=e[2],b[g+3]^=e[3];return{tag:d,data:h.clamp(b,k)}}}; f-1)],c).concat([0,0,0]).slice(0,4);d=h.bitSlice(g(d,a.encrypt(c)),0,e);if(!l)return{tag:d,data:[]};for(g=0;g<l;g+=4)c[3]++,e=a.encrypt(c),b[g]^=e[0],b[g+1]^=e[1],b[g+2]^=e[2],b[g+3]^=e[3];return{tag:d,data:h.clamp(b,k)}}};
sjcl.mode.ocb2={name:"ocb2",encrypt:function(a,b,c,d,e,f){128!==sjcl.bitArray.bitLength(c)&&q(new sjcl.exception.invalid("ocb iv must be 128 bits"));var g,h=sjcl.mode.ocb2.G,l=sjcl.bitArray,k=l.k,n=[0,0,0,0];c=h(a.encrypt(c));var m,p=[];d=d||[];e=e||64;for(g=0;g+4<b.length;g+=4)m=b.slice(g,g+4),n=k(n,m),p=p.concat(k(c,a.encrypt(k(c,m)))),c=h(c);m=b.slice(g);b=l.bitLength(m);g=a.encrypt(k(c,[0,0,0,b]));m=l.clamp(k(m.concat([0,0,0]),g),b);n=k(n,k(m.concat([0,0,0]),g));n=a.encrypt(k(n,k(c,h(c))));d.length&& sjcl.mode.ocb2={name:"ocb2",encrypt:function(a,b,c,d,e,f){128!==sjcl.bitArray.bitLength(c)&&q(new sjcl.exception.invalid("ocb iv must be 128 bits"));var g,h=sjcl.mode.ocb2.H,l=sjcl.bitArray,k=l.l,n=[0,0,0,0];c=h(a.encrypt(c));var m,p=[];d=d||[];e=e||64;for(g=0;g+4<b.length;g+=4)m=b.slice(g,g+4),n=k(n,m),p=p.concat(k(c,a.encrypt(k(c,m)))),c=h(c);m=b.slice(g);b=l.bitLength(m);g=a.encrypt(k(c,[0,0,0,b]));m=l.clamp(k(m.concat([0,0,0]),g),b);n=k(n,k(m.concat([0,0,0]),g));n=a.encrypt(k(n,k(c,h(c))));d.length&&
(n=k(n,f?d:sjcl.mode.ocb2.pmac(a,d)));return p.concat(l.concat(m,l.clamp(n,e)))},decrypt:function(a,b,c,d,e,f){128!==sjcl.bitArray.bitLength(c)&&q(new sjcl.exception.invalid("ocb iv must be 128 bits"));e=e||64;var g=sjcl.mode.ocb2.G,h=sjcl.bitArray,l=h.k,k=[0,0,0,0],n=g(a.encrypt(c)),m,p,s=sjcl.bitArray.bitLength(b)-e,r=[];d=d||[];for(c=0;c+4<s/32;c+=4)m=l(n,a.decrypt(l(n,b.slice(c,c+4)))),k=l(k,m),r=r.concat(m),n=g(n);p=s-32*c;m=a.encrypt(l(n,[0,0,0,p]));m=l(m,h.clamp(b.slice(c),p).concat([0,0,0])); (n=k(n,f?d:sjcl.mode.ocb2.pmac(a,d)));return p.concat(l.concat(m,l.clamp(n,e)))},decrypt:function(a,b,c,d,e,f){128!==sjcl.bitArray.bitLength(c)&&q(new sjcl.exception.invalid("ocb iv must be 128 bits"));e=e||64;var g=sjcl.mode.ocb2.H,h=sjcl.bitArray,l=h.l,k=[0,0,0,0],n=g(a.encrypt(c)),m,p,s=sjcl.bitArray.bitLength(b)-e,r=[];d=d||[];for(c=0;c+4<s/32;c+=4)m=l(n,a.decrypt(l(n,b.slice(c,c+4)))),k=l(k,m),r=r.concat(m),n=g(n);p=s-32*c;m=a.encrypt(l(n,[0,0,0,p]));m=l(m,h.clamp(b.slice(c),p).concat([0,0,0]));
k=l(k,m);k=a.encrypt(l(k,l(n,g(n))));d.length&&(k=l(k,f?d:sjcl.mode.ocb2.pmac(a,d)));h.equal(h.clamp(k,e),h.bitSlice(b,s))||q(new sjcl.exception.corrupt("ocb: tag doesn't match"));return r.concat(h.clamp(m,p))},pmac:function(a,b){var c,d=sjcl.mode.ocb2.G,e=sjcl.bitArray,f=e.k,g=[0,0,0,0],h=a.encrypt([0,0,0,0]),h=f(h,d(d(h)));for(c=0;c+4<b.length;c+=4)h=d(h),g=f(g,a.encrypt(f(h,b.slice(c,c+4))));c=b.slice(c);128>e.bitLength(c)&&(h=f(h,d(h)),c=e.concat(c,[-2147483648,0,0,0]));g=f(g,c);return a.encrypt(f(d(f(h, k=l(k,m);k=a.encrypt(l(k,l(n,g(n))));d.length&&(k=l(k,f?d:sjcl.mode.ocb2.pmac(a,d)));h.equal(h.clamp(k,e),h.bitSlice(b,s))||q(new sjcl.exception.corrupt("ocb: tag doesn't match"));return r.concat(h.clamp(m,p))},pmac:function(a,b){var c,d=sjcl.mode.ocb2.H,e=sjcl.bitArray,f=e.l,g=[0,0,0,0],h=a.encrypt([0,0,0,0]),h=f(h,d(d(h)));for(c=0;c+4<b.length;c+=4)h=d(h),g=f(g,a.encrypt(f(h,b.slice(c,c+4))));c=b.slice(c);128>e.bitLength(c)&&(h=f(h,d(h)),c=e.concat(c,[-2147483648,0,0,0]));g=f(g,c);return a.encrypt(f(d(f(h,
d(h))),g))},G:function(a){return[a[0]<<1^a[1]>>>31,a[1]<<1^a[2]>>>31,a[2]<<1^a[3]>>>31,a[3]<<1^135*(a[0]>>>31)]}}; d(h))),g))},H:function(a){return[a[0]<<1^a[1]>>>31,a[1]<<1^a[2]>>>31,a[2]<<1^a[3]>>>31,a[3]<<1^135*(a[0]>>>31)]}};
sjcl.mode.gcm={name:"gcm",encrypt:function(a,b,c,d,e){var f=b.slice(0);b=sjcl.bitArray;d=d||[];a=sjcl.mode.gcm.n(!0,a,f,d,c,e||128);return b.concat(a.data,a.tag)},decrypt:function(a,b,c,d,e){var f=b.slice(0),g=sjcl.bitArray,h=g.bitLength(f);e=e||128;d=d||[];e<=h?(b=g.bitSlice(f,h-e),f=g.bitSlice(f,0,h-e)):(b=f,f=[]);a=sjcl.mode.gcm.n(u,a,f,d,c,e);g.equal(a.tag,b)||q(new sjcl.exception.corrupt("gcm: tag doesn't match"));return a.data},U:function(a,b){var c,d,e,f,g,h=sjcl.bitArray.k;e=[0,0,0,0];f=b.slice(0); sjcl.mode.gcm={name:"gcm",encrypt:function(a,b,c,d,e){var f=b.slice(0);b=sjcl.bitArray;d=d||[];a=sjcl.mode.gcm.p(!0,a,f,d,c,e||128);return b.concat(a.data,a.tag)},decrypt:function(a,b,c,d,e){var f=b.slice(0),g=sjcl.bitArray,h=g.bitLength(f);e=e||128;d=d||[];e<=h?(b=g.bitSlice(f,h-e),f=g.bitSlice(f,0,h-e)):(b=f,f=[]);a=sjcl.mode.gcm.p(u,a,f,d,c,e);g.equal(a.tag,b)||q(new sjcl.exception.corrupt("gcm: tag doesn't match"));return a.data},Z:function(a,b){var c,d,e,f,g,h=sjcl.bitArray.l;e=[0,0,0,0];f=b.slice(0);
for(c=0;128>c;c++){(d=0!==(a[Math.floor(c/32)]&1<<31-c%32))&&(e=h(e,f));g=0!==(f[3]&1);for(d=3;0<d;d--)f[d]=f[d]>>>1|(f[d-1]&1)<<31;f[0]>>>=1;g&&(f[0]^=-0x1f000000)}return e},f:function(a,b,c){var d,e=c.length;b=b.slice(0);for(d=0;d<e;d+=4)b[0]^=0xffffffff&c[d],b[1]^=0xffffffff&c[d+1],b[2]^=0xffffffff&c[d+2],b[3]^=0xffffffff&c[d+3],b=sjcl.mode.gcm.U(b,a);return b},n:function(a,b,c,d,e,f){var g,h,l,k,n,m,p,s,r=sjcl.bitArray;m=c.length;p=r.bitLength(c);s=r.bitLength(d);h=r.bitLength(e);g=b.encrypt([0, for(c=0;128>c;c++){(d=0!==(a[Math.floor(c/32)]&1<<31-c%32))&&(e=h(e,f));g=0!==(f[3]&1);for(d=3;0<d;d--)f[d]=f[d]>>>1|(f[d-1]&1)<<31;f[0]>>>=1;g&&(f[0]^=-0x1f000000)}return e},g:function(a,b,c){var d,e=c.length;b=b.slice(0);for(d=0;d<e;d+=4)b[0]^=0xffffffff&c[d],b[1]^=0xffffffff&c[d+1],b[2]^=0xffffffff&c[d+2],b[3]^=0xffffffff&c[d+3],b=sjcl.mode.gcm.Z(b,a);return b},p:function(a,b,c,d,e,f){var g,h,l,k,n,m,p,s,r=sjcl.bitArray;m=c.length;p=r.bitLength(c);s=r.bitLength(d);h=r.bitLength(e);g=b.encrypt([0,
0,0,0]);96===h?(e=e.slice(0),e=r.concat(e,[1])):(e=sjcl.mode.gcm.f(g,[0,0,0,0],e),e=sjcl.mode.gcm.f(g,e,[0,0,Math.floor(h/0x100000000),h&0xffffffff]));h=sjcl.mode.gcm.f(g,[0,0,0,0],d);n=e.slice(0);d=h.slice(0);a||(d=sjcl.mode.gcm.f(g,h,c));for(k=0;k<m;k+=4)n[3]++,l=b.encrypt(n),c[k]^=l[0],c[k+1]^=l[1],c[k+2]^=l[2],c[k+3]^=l[3];c=r.clamp(c,p);a&&(d=sjcl.mode.gcm.f(g,h,c));a=[Math.floor(s/0x100000000),s&0xffffffff,Math.floor(p/0x100000000),p&0xffffffff];d=sjcl.mode.gcm.f(g,d,a);l=b.encrypt(e);d[0]^=l[0]; 0,0,0]);96===h?(e=e.slice(0),e=r.concat(e,[1])):(e=sjcl.mode.gcm.g(g,[0,0,0,0],e),e=sjcl.mode.gcm.g(g,e,[0,0,Math.floor(h/0x100000000),h&0xffffffff]));h=sjcl.mode.gcm.g(g,[0,0,0,0],d);n=e.slice(0);d=h.slice(0);a||(d=sjcl.mode.gcm.g(g,h,c));for(k=0;k<m;k+=4)n[3]++,l=b.encrypt(n),c[k]^=l[0],c[k+1]^=l[1],c[k+2]^=l[2],c[k+3]^=l[3];c=r.clamp(c,p);a&&(d=sjcl.mode.gcm.g(g,h,c));a=[Math.floor(s/0x100000000),s&0xffffffff,Math.floor(p/0x100000000),p&0xffffffff];d=sjcl.mode.gcm.g(g,d,a);l=b.encrypt(e);d[0]^=l[0];
d[1]^=l[1];d[2]^=l[2];d[3]^=l[3];return{tag:r.bitSlice(d,0,f),data:c}}};sjcl.misc.hmac=function(a,b){this.L=b=b||sjcl.hash.sha256;var c=[[],[]],d,e=b.prototype.blockSize/32;this.o=[new b,new b];a.length>e&&(a=b.hash(a));for(d=0;d<e;d++)c[0][d]=a[d]^909522486,c[1][d]=a[d]^1549556828;this.o[0].update(c[0]);this.o[1].update(c[1])};sjcl.misc.hmac.prototype.encrypt=sjcl.misc.hmac.prototype.mac=function(a){a=(new this.L(this.o[0])).update(a).finalize();return(new this.L(this.o[1])).update(a).finalize()}; d[1]^=l[1];d[2]^=l[2];d[3]^=l[3];return{tag:r.bitSlice(d,0,f),data:c}}};sjcl.misc.hmac=function(a,b){this.M=b=b||sjcl.hash.sha256;var c=[[],[]],d,e=b.prototype.blockSize/32;this.n=[new b,new b];a.length>e&&(a=b.hash(a));for(d=0;d<e;d++)c[0][d]=a[d]^909522486,c[1][d]=a[d]^1549556828;this.n[0].update(c[0]);this.n[1].update(c[1]);this.G=new b(this.n[0])};
sjcl.misc.pbkdf2=function(a,b,c,d,e){c=c||1E3;(0>d||0>c)&&q(sjcl.exception.invalid("invalid params to pbkdf2"));"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));e=e||sjcl.misc.hmac;a=new e(a);var f,g,h,l,k=[],n=sjcl.bitArray;for(l=1;32*k.length<(d||1);l++){e=f=a.encrypt(n.concat(b,[l]));for(g=1;g<c;g++){f=a.encrypt(f);for(h=0;h<f.length;h++)e[h]^=f[h]}k=k.concat(e)}d&&(k=n.clamp(k,d));return k}; sjcl.misc.hmac.prototype.encrypt=sjcl.misc.hmac.prototype.mac=function(a){this.Q&&q(new sjcl.exception.invalid("encrypt on already updated hmac called!"));this.update(a);return this.digest(a)};sjcl.misc.hmac.prototype.reset=function(){this.G=new this.M(this.n[0]);this.Q=u};sjcl.misc.hmac.prototype.update=function(a){this.Q=!0;this.G.update(a)};sjcl.misc.hmac.prototype.digest=function(){var a=this.G.finalize(),a=(new this.M(this.n[1])).update(a).finalize();this.reset();return a};
sjcl.prng=function(a){this.b=[new sjcl.hash.sha256];this.h=[0];this.F=0;this.t={};this.C=0;this.J={};this.N=this.c=this.i=this.T=0;this.a=[0,0,0,0,0,0,0,0];this.e=[0,0,0,0];this.A=t;this.B=a;this.p=u;this.z={progress:{},seeded:{}};this.l=this.S=0;this.u=1;this.w=2;this.Q=0x10000;this.H=[0,48,64,96,128,192,0x100,384,512,768,1024];this.R=3E4;this.P=80}; sjcl.misc.pbkdf2=function(a,b,c,d,e){c=c||1E3;(0>d||0>c)&&q(sjcl.exception.invalid("invalid params to pbkdf2"));"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));"string"===typeof b&&(b=sjcl.codec.utf8String.toBits(b));e=e||sjcl.misc.hmac;a=new e(a);var f,g,h,l,k=[],n=sjcl.bitArray;for(l=1;32*k.length<(d||1);l++){e=f=a.encrypt(n.concat(b,[l]));for(g=1;g<c;g++){f=a.encrypt(f);for(h=0;h<f.length;h++)e[h]^=f[h]}k=k.concat(e)}d&&(k=n.clamp(k,d));return k};
sjcl.prng.prototype={randomWords:function(a,b){var c=[],d;d=this.isReady(b);var e;d===this.l&&q(new sjcl.exception.notReady("generator isn't seeded"));if(d&this.w){d=!(d&this.u);e=[];var f=0,g;this.N=e[0]=(new Date).valueOf()+this.R;for(g=0;16>g;g++)e.push(0x100000000*Math.random()|0);for(g=0;g<this.b.length&&!(e=e.concat(this.b[g].finalize()),f+=this.h[g],this.h[g]=0,!d&&this.F&1<<g);g++);this.F>=1<<this.b.length&&(this.b.push(new sjcl.hash.sha256),this.h.push(0));this.c-=f;f>this.i&&(this.i=f);this.F++; sjcl.prng=function(a){this.c=[new sjcl.hash.sha256];this.i=[0];this.F=0;this.s={};this.C=0;this.K={};this.O=this.d=this.j=this.W=0;this.b=[0,0,0,0,0,0,0,0];this.f=[0,0,0,0];this.A=t;this.B=a;this.q=u;this.w={progress:{},seeded:{}};this.m=this.V=0;this.t=1;this.u=2;this.S=0x10000;this.I=[0,48,64,96,128,192,0x100,384,512,768,1024];this.T=3E4;this.R=80};
this.a=sjcl.hash.sha256.hash(this.a.concat(e));this.A=new sjcl.cipher.aes(this.a);for(d=0;4>d&&!(this.e[d]=this.e[d]+1|0,this.e[d]);d++);}for(d=0;d<a;d+=4)0===(d+1)%this.Q&&A(this),e=B(this),c.push(e[0],e[1],e[2],e[3]);A(this);return c.slice(0,a)},setDefaultParanoia:function(a){this.B=a},addEntropy:function(a,b,c){c=c||"user";var d,e,f=(new Date).valueOf(),g=this.t[c],h=this.isReady(),l=0;d=this.J[c];d===t&&(d=this.J[c]=this.T++);g===t&&(g=this.t[c]=0);this.t[c]=(this.t[c]+1)%this.b.length;switch(typeof a){case "number":b=== sjcl.prng.prototype={randomWords:function(a,b){var c=[],d;d=this.isReady(b);var e;d===this.m&&q(new sjcl.exception.notReady("generator isn't seeded"));if(d&this.u){d=!(d&this.t);e=[];var f=0,g;this.O=e[0]=(new Date).valueOf()+this.T;for(g=0;16>g;g++)e.push(0x100000000*Math.random()|0);for(g=0;g<this.c.length&&!(e=e.concat(this.c[g].finalize()),f+=this.i[g],this.i[g]=0,!d&&this.F&1<<g);g++);this.F>=1<<this.c.length&&(this.c.push(new sjcl.hash.sha256),this.i.push(0));this.d-=f;f>this.j&&(this.j=f);this.F++;
t&&(b=1);this.b[g].update([d,this.C++,1,b,f,1,a|0]);break;case "object":c=Object.prototype.toString.call(a);if("[object Uint32Array]"===c){e=[];for(c=0;c<a.length;c++)e.push(a[c]);a=e}else{"[object Array]"!==c&&(l=1);for(c=0;c<a.length&&!l;c++)"number"!=typeof a[c]&&(l=1)}if(!l){if(b===t)for(c=b=0;c<a.length;c++)for(e=a[c];0<e;)b++,e>>>=1;this.b[g].update([d,this.C++,2,b,f,a.length].concat(a))}break;case "string":b===t&&(b=a.length);this.b[g].update([d,this.C++,3,b,f,a.length]);this.b[g].update(a); this.b=sjcl.hash.sha256.hash(this.b.concat(e));this.A=new sjcl.cipher.aes(this.b);for(d=0;4>d&&!(this.f[d]=this.f[d]+1|0,this.f[d]);d++);}for(d=0;d<a;d+=4)0===(d+1)%this.S&&A(this),e=B(this),c.push(e[0],e[1],e[2],e[3]);A(this);return c.slice(0,a)},setDefaultParanoia:function(a,b){0===a&&"Setting paranoia=0 will ruin your security; use it only for testing"!==b&&q("Setting paranoia=0 will ruin your security; use it only for testing");this.B=a},addEntropy:function(a,b,c){c=c||"user";var d,e,f=(new Date).valueOf(),
break;default:l=1}l&&q(new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string"));this.h[g]+=b;this.c+=b;h===this.l&&(this.isReady()!==this.l&&C("seeded",Math.max(this.i,this.c)),C("progress",this.getProgress()))},isReady:function(a){a=this.H[a!==t?a:this.B];return this.i&&this.i>=a?this.h[0]>this.P&&(new Date).valueOf()>this.N?this.w|this.u:this.u:this.c>=a?this.w|this.l:this.l},getProgress:function(a){a=this.H[a?a:this.B];return this.i>=a?1:this.c>a?1:this.c/ g=this.s[c],h=this.isReady(),l=0;d=this.K[c];d===t&&(d=this.K[c]=this.W++);g===t&&(g=this.s[c]=0);this.s[c]=(this.s[c]+1)%this.c.length;switch(typeof a){case "number":b===t&&(b=1);this.c[g].update([d,this.C++,1,b,f,1,a|0]);break;case "object":c=Object.prototype.toString.call(a);if("[object Uint32Array]"===c){e=[];for(c=0;c<a.length;c++)e.push(a[c]);a=e}else{"[object Array]"!==c&&(l=1);for(c=0;c<a.length&&!l;c++)"number"!==typeof a[c]&&(l=1)}if(!l){if(b===t)for(c=b=0;c<a.length;c++)for(e=a[c];0<e;)b++,
a},startCollectors:function(){this.p||(window.addEventListener?(window.addEventListener("load",this.r,u),window.addEventListener("mousemove",this.s,u)):document.attachEvent?(document.attachEvent("onload",this.r),document.attachEvent("onmousemove",this.s)):q(new sjcl.exception.bug("can't attach event")),this.p=!0)},stopCollectors:function(){this.p&&(window.removeEventListener?(window.removeEventListener("load",this.r,u),window.removeEventListener("mousemove",this.s,u)):window.detachEvent&&(window.detachEvent("onload", e>>>=1;this.c[g].update([d,this.C++,2,b,f,a.length].concat(a))}break;case "string":b===t&&(b=a.length);this.c[g].update([d,this.C++,3,b,f,a.length]);this.c[g].update(a);break;default:l=1}l&&q(new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string"));this.i[g]+=b;this.d+=b;h===this.m&&(this.isReady()!==this.m&&C("seeded",Math.max(this.j,this.d)),C("progress",this.getProgress()))},isReady:function(a){a=this.I[a!==t?a:this.B];return this.j&&this.j>=a?this.i[0]>this.R&&
this.r),window.detachEvent("onmousemove",this.s)),this.p=u)},addEventListener:function(a,b){this.z[a][this.S++]=b},removeEventListener:function(a,b){var c,d,e=this.z[a],f=[];for(d in e)e.hasOwnProperty(d)&&e[d]===b&&f.push(d);for(c=0;c<f.length;c++)d=f[c],delete e[d]},s:function(a){sjcl.random.addEntropy([a.x||a.clientX||a.offsetX||0,a.y||a.clientY||a.offsetY||0],2,"mouse")},r:function(){sjcl.random.addEntropy((new Date).valueOf(),2,"loadtime")}}; (new Date).valueOf()>this.O?this.u|this.t:this.t:this.d>=a?this.u|this.m:this.m},getProgress:function(a){a=this.I[a?a:this.B];return this.j>=a?1:this.d>a?1:this.d/a},startCollectors:function(){this.q||(this.a={loadTimeCollector:D(this,this.aa),mouseCollector:D(this,this.ba),keyboardCollector:D(this,this.$),accelerometerCollector:D(this,this.U)},window.addEventListener?(window.addEventListener("load",this.a.loadTimeCollector,u),window.addEventListener("mousemove",this.a.mouseCollector,u),window.addEventListener("keypress",
function C(a,b){var c,d=sjcl.random.z[a],e=[];for(c in d)d.hasOwnProperty(c)&&e.push(d[c]);for(c=0;c<e.length;c++)e[c](b)}function A(a){a.a=B(a).concat(B(a));a.A=new sjcl.cipher.aes(a.a)}function B(a){for(var b=0;4>b&&!(a.e[b]=a.e[b]+1|0,a.e[b]);b++);return a.A.encrypt(a.e)}sjcl.random=new sjcl.prng(6);try{var D=new Uint32Array(32);crypto.getRandomValues(D);sjcl.random.addEntropy(D,1024,"crypto['getRandomValues']")}catch(E){} this.a.keyboardCollector,u),window.addEventListener("devicemotion",this.a.accelerometerCollector,u)):document.attachEvent?(document.attachEvent("onload",this.a.loadTimeCollector),document.attachEvent("onmousemove",this.a.mouseCollector),document.attachEvent("keypress",this.a.keyboardCollector)):q(new sjcl.exception.bug("can't attach event")),this.q=!0)},stopCollectors:function(){this.q&&(window.removeEventListener?(window.removeEventListener("load",this.a.loadTimeCollector,u),window.removeEventListener("mousemove",
sjcl.json={defaults:{v:1,iter:1E3,ks:128,ts:64,mode:"ccm",adata:"",cipher:"aes"},encrypt:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json,f=e.d({iv:sjcl.random.randomWords(4,0)},e.defaults),g;e.d(f,c);c=f.adata;"string"===typeof f.salt&&(f.salt=sjcl.codec.base64.toBits(f.salt));"string"===typeof f.iv&&(f.iv=sjcl.codec.base64.toBits(f.iv));(!sjcl.mode[f.mode]||!sjcl.cipher[f.cipher]||"string"===typeof a&&100>=f.iter||64!==f.ts&&96!==f.ts&&128!==f.ts||128!==f.ks&&192!==f.ks&&0x100!==f.ks||2>f.iv.length|| this.a.mouseCollector,u),window.removeEventListener("keypress",this.a.keyboardCollector,u),window.removeEventListener("devicemotion",this.a.accelerometerCollector,u)):document.detachEvent&&(document.detachEvent("onload",this.a.loadTimeCollector),document.detachEvent("onmousemove",this.a.mouseCollector),document.detachEvent("keypress",this.a.keyboardCollector)),this.q=u)},addEventListener:function(a,b){this.w[a][this.V++]=b},removeEventListener:function(a,b){var c,d,e=this.w[a],f=[];for(d in e)e.hasOwnProperty(d)&&
4<f.iv.length)&&q(new sjcl.exception.invalid("json encrypt: invalid parameters"));"string"===typeof a?(g=sjcl.misc.cachedPbkdf2(a,f),a=g.key.slice(0,f.ks/32),f.salt=g.salt):sjcl.ecc&&a instanceof sjcl.ecc.elGamal.publicKey&&(g=a.kem(),f.kemtag=g.tag,a=g.key.slice(0,f.ks/32));"string"===typeof b&&(b=sjcl.codec.utf8String.toBits(b));"string"===typeof c&&(c=sjcl.codec.utf8String.toBits(c));g=new sjcl.cipher[f.cipher](a);e.d(d,f);d.key=a;f.ct=sjcl.mode[f.mode].encrypt(g,b,f.iv,c,f.ts);return e.encode(f)}, e[d]===b&&f.push(d);for(c=0;c<f.length;c++)d=f[c],delete e[d]},$:function(){E(1)},ba:function(a){var b,c;try{b=a.x||a.clientX||a.offsetX||0,c=a.y||a.clientY||a.offsetY||0}catch(d){c=b=0}0!=b&&0!=c&&sjcl.random.addEntropy([b,c],2,"mouse");E(0)},aa:function(){E(2)},U:function(a){a=a.accelerationIncludingGravity.x||a.accelerationIncludingGravity.y||a.accelerationIncludingGravity.z;if(window.orientation){var b=window.orientation;"number"===typeof b&&sjcl.random.addEntropy(b,1,"accelerometer")}a&&sjcl.random.addEntropy(a,
decrypt:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json;b=e.d(e.d(e.d({},e.defaults),e.decode(b)),c,!0);var f;c=b.adata;"string"===typeof b.salt&&(b.salt=sjcl.codec.base64.toBits(b.salt));"string"===typeof b.iv&&(b.iv=sjcl.codec.base64.toBits(b.iv));(!sjcl.mode[b.mode]||!sjcl.cipher[b.cipher]||"string"===typeof a&&100>=b.iter||64!==b.ts&&96!==b.ts&&128!==b.ts||128!==b.ks&&192!==b.ks&&0x100!==b.ks||!b.iv||2>b.iv.length||4<b.iv.length)&&q(new sjcl.exception.invalid("json decrypt: invalid parameters")); 2,"accelerometer");E(0)}};function C(a,b){var c,d=sjcl.random.w[a],e=[];for(c in d)d.hasOwnProperty(c)&&e.push(d[c]);for(c=0;c<e.length;c++)e[c](b)}function E(a){"undefined"!==typeof window&&window.performance&&"function"===typeof window.performance.now?sjcl.random.addEntropy(window.performance.now(),a,"loadtime"):sjcl.random.addEntropy((new Date).valueOf(),a,"loadtime")}function A(a){a.b=B(a).concat(B(a));a.A=new sjcl.cipher.aes(a.b)}
"string"===typeof a?(f=sjcl.misc.cachedPbkdf2(a,b),a=f.key.slice(0,b.ks/32),b.salt=f.salt):sjcl.ecc&&a instanceof sjcl.ecc.elGamal.secretKey&&(a=a.unkem(sjcl.codec.base64.toBits(b.kemtag)).slice(0,b.ks/32));"string"===typeof c&&(c=sjcl.codec.utf8String.toBits(c));f=new sjcl.cipher[b.cipher](a);c=sjcl.mode[b.mode].decrypt(f,b.ct,b.iv,c,b.ts);e.d(d,b);d.key=a;return sjcl.codec.utf8String.fromBits(c)},encode:function(a){var b,c="{",d="";for(b in a)if(a.hasOwnProperty(b))switch(b.match(/^[a-z0-9]+$/i)|| function B(a){for(var b=0;4>b&&!(a.f[b]=a.f[b]+1|0,a.f[b]);b++);return a.A.encrypt(a.f)}function D(a,b){return function(){b.apply(a,arguments)}}sjcl.random=new sjcl.prng(6);
q(new sjcl.exception.invalid("json encode: invalid property name")),c+=d+'"'+b+'":',d=",",typeof a[b]){case "number":case "boolean":c+=a[b];break;case "string":c+='"'+escape(a[b])+'"';break;case "object":c+='"'+sjcl.codec.base64.fromBits(a[b],0)+'"';break;default:q(new sjcl.exception.bug("json encode: unsupported type"))}return c+"}"},decode:function(a){a=a.replace(/\s/g,"");a.match(/^\{.*\}$/)||q(new sjcl.exception.invalid("json decode: this isn't json!"));a=a.replace(/^\{|\}$/g,"").split(/,/);var b= a:try{var F,G,H,I;if(I="undefined"!==typeof module){var J;if(J=module.exports){var K;try{K=require("crypto")}catch(L){K=null}J=(G=K)&&G.randomBytes}I=J}if(I)F=G.randomBytes(128),F=new Uint32Array((new Uint8Array(F)).buffer),sjcl.random.addEntropy(F,1024,"crypto['randomBytes']");else if("undefined"!==typeof window&&"undefined"!==typeof Uint32Array){H=new Uint32Array(32);if(window.crypto&&window.crypto.getRandomValues)window.crypto.getRandomValues(H);else if(window.msCrypto&&window.msCrypto.getRandomValues)window.msCrypto.getRandomValues(H);
{},c,d;for(c=0;c<a.length;c++)(d=a[c].match(/^(?:(["']?)([a-z][a-z0-9]*)\1):(?:(\d+)|"([a-z0-9+\/%*_.@=\-]*)")$/i))||q(new sjcl.exception.invalid("json decode: this isn't json!")),b[d[2]]=d[3]?parseInt(d[3],10):d[2].match(/^(ct|salt|iv)$/)?sjcl.codec.base64.toBits(d[4]):unescape(d[4]);return b},d:function(a,b,c){a===t&&(a={});if(b===t)return a;for(var d in b)b.hasOwnProperty(d)&&(c&&(a[d]!==t&&a[d]!==b[d])&&q(new sjcl.exception.invalid("required parameter overridden")),a[d]=b[d]);return a},X:function(a, else break a;sjcl.random.addEntropy(H,1024,"crypto['getRandomValues']")}}catch(M){"undefined"!==typeof window&&window.console&&(console.log("There was an error collecting entropy from the browser:"),console.log(M))}
b){var c={},d;for(d in a)a.hasOwnProperty(d)&&a[d]!==b[d]&&(c[d]=a[d]);return c},W:function(a,b){var c={},d;for(d=0;d<b.length;d++)a[b[d]]!==t&&(c[b[d]]=a[b[d]]);return c}};sjcl.encrypt=sjcl.json.encrypt;sjcl.decrypt=sjcl.json.decrypt;sjcl.misc.V={}; sjcl.json={defaults:{v:1,iter:1E3,ks:128,ts:64,mode:"ccm",adata:"",cipher:"aes"},Y:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json,f=e.e({iv:sjcl.random.randomWords(4,0)},e.defaults),g;e.e(f,c);c=f.adata;"string"===typeof f.salt&&(f.salt=sjcl.codec.base64.toBits(f.salt));"string"===typeof f.iv&&(f.iv=sjcl.codec.base64.toBits(f.iv));(!sjcl.mode[f.mode]||!sjcl.cipher[f.cipher]||"string"===typeof a&&100>=f.iter||64!==f.ts&&96!==f.ts&&128!==f.ts||128!==f.ks&&192!==f.ks&&0x100!==f.ks||2>f.iv.length||4<
sjcl.misc.cachedPbkdf2=function(a,b){var c=sjcl.misc.V,d;b=b||{};d=b.iter||1E3;c=c[a]=c[a]||{};d=c[d]=c[d]||{firstSalt:b.salt&&b.salt.length?b.salt.slice(0):sjcl.random.randomWords(2,0)};c=b.salt===t?d.firstSalt:b.salt;d[c]=d[c]||sjcl.misc.pbkdf2(a,c,b.iter);return{key:d[c].slice(0),salt:c.slice(0)}}; f.iv.length)&&q(new sjcl.exception.invalid("json encrypt: invalid parameters"));"string"===typeof a?(g=sjcl.misc.cachedPbkdf2(a,f),a=g.key.slice(0,f.ks/32),f.salt=g.salt):sjcl.ecc&&a instanceof sjcl.ecc.elGamal.publicKey&&(g=a.kem(),f.kemtag=g.tag,a=g.key.slice(0,f.ks/32));"string"===typeof b&&(b=sjcl.codec.utf8String.toBits(b));"string"===typeof c&&(c=sjcl.codec.utf8String.toBits(c));g=new sjcl.cipher[f.cipher](a);e.e(d,f);d.key=a;f.ct=sjcl.mode[f.mode].encrypt(g,b,f.iv,c,f.ts);return f},encrypt:function(a,
b,c,d){var e=sjcl.json,f=e.Y.apply(e,arguments);return e.encode(f)},X:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json;b=e.e(e.e(e.e({},e.defaults),b),c,!0);var f,g;f=b.adata;"string"===typeof b.salt&&(b.salt=sjcl.codec.base64.toBits(b.salt));"string"===typeof b.iv&&(b.iv=sjcl.codec.base64.toBits(b.iv));(!sjcl.mode[b.mode]||!sjcl.cipher[b.cipher]||"string"===typeof a&&100>=b.iter||64!==b.ts&&96!==b.ts&&128!==b.ts||128!==b.ks&&192!==b.ks&&0x100!==b.ks||!b.iv||2>b.iv.length||4<b.iv.length)&&q(new sjcl.exception.invalid("json decrypt: invalid parameters"));
"string"===typeof a?(g=sjcl.misc.cachedPbkdf2(a,b),a=g.key.slice(0,b.ks/32),b.salt=g.salt):sjcl.ecc&&a instanceof sjcl.ecc.elGamal.secretKey&&(a=a.unkem(sjcl.codec.base64.toBits(b.kemtag)).slice(0,b.ks/32));"string"===typeof f&&(f=sjcl.codec.utf8String.toBits(f));g=new sjcl.cipher[b.cipher](a);f=sjcl.mode[b.mode].decrypt(g,b.ct,b.iv,f,b.ts);e.e(d,b);d.key=a;return 1===c.raw?f:sjcl.codec.utf8String.fromBits(f)},decrypt:function(a,b,c,d){var e=sjcl.json;return e.X(a,e.decode(b),c,d)},encode:function(a){var b,
c="{",d="";for(b in a)if(a.hasOwnProperty(b))switch(b.match(/^[a-z0-9]+$/i)||q(new sjcl.exception.invalid("json encode: invalid property name")),c+=d+'"'+b+'":',d=",",typeof a[b]){case "number":case "boolean":c+=a[b];break;case "string":c+='"'+escape(a[b])+'"';break;case "object":c+='"'+sjcl.codec.base64.fromBits(a[b],0)+'"';break;default:q(new sjcl.exception.bug("json encode: unsupported type"))}return c+"}"},decode:function(a){a=a.replace(/\s/g,"");a.match(/^\{.*\}$/)||q(new sjcl.exception.invalid("json decode: this isn't json!"));
a=a.replace(/^\{|\}$/g,"").split(/,/);var b={},c,d;for(c=0;c<a.length;c++)(d=a[c].match(/^(?:(["']?)([a-z][a-z0-9]*)\1):(?:(\d+)|"([a-z0-9+\/%*_.@=\-]*)")$/i))||q(new sjcl.exception.invalid("json decode: this isn't json!")),b[d[2]]=d[3]?parseInt(d[3],10):d[2].match(/^(ct|salt|iv)$/)?sjcl.codec.base64.toBits(d[4]):unescape(d[4]);return b},e:function(a,b,c){a===t&&(a={});if(b===t)return a;for(var d in b)b.hasOwnProperty(d)&&(c&&(a[d]!==t&&a[d]!==b[d])&&q(new sjcl.exception.invalid("required parameter overridden")),
a[d]=b[d]);return a},ea:function(a,b){var c={},d;for(d in a)a.hasOwnProperty(d)&&a[d]!==b[d]&&(c[d]=a[d]);return c},da:function(a,b){var c={},d;for(d=0;d<b.length;d++)a[b[d]]!==t&&(c[b[d]]=a[b[d]]);return c}};sjcl.encrypt=sjcl.json.encrypt;sjcl.decrypt=sjcl.json.decrypt;sjcl.misc.ca={};
sjcl.misc.cachedPbkdf2=function(a,b){var c=sjcl.misc.ca,d;b=b||{};d=b.iter||1E3;c=c[a]=c[a]||{};d=c[d]=c[d]||{firstSalt:b.salt&&b.salt.length?b.salt.slice(0):sjcl.random.randomWords(2,0)};c=b.salt===t?d.firstSalt:b.salt;d[c]=d[c]||sjcl.misc.pbkdf2(a,c,b.iter);return{key:d[c].slice(0),salt:c.slice(0)}};

View File

@@ -0,0 +1,115 @@
(function() {
function word2hex(w) {
return "0x" + ((w|0)+0xF00000000000).toString(16).substr(4);
}
var b0 = sjcl.bitArray.partial(1, 0);
var b1 = sjcl.bitArray.partial(1, 1);
function concatbits(s) {
var j, b, a = [];
for (j = 0; j < s.length; ++j) {
b = (s[j] == '1' ? b1 : b0);
a = sjcl.bitArray.concat(a, [b]);
}
return a;
}
new sjcl.test.TestCase("bitArray single bits", function (cb) {
if (!sjcl.bitArray) {
this.unimplemented();
cb && cb();
return;
}
this.require((b0|0) === (0x00000000|0), "bitstring '0': " + word2hex(b0));
this.require((b1|0) === (0x80000000|0), "bitstring '1': " + word2hex(b1));
cb && cb();
});
new sjcl.test.TestCase("bitArray concat small bitstrings", function (cb) {
if (!sjcl.bitArray) {
this.unimplemented();
cb && cb();
return;
}
var i, kat = sjcl.test.vector.bitArray.bits, tv, a, b, bitlen, t;
for (i=0; i<kat.length; i++) {
tv = kat[i];
a = concatbits(tv[0]);
bitlen = sjcl.bitArray.bitLength(a);
t = "bitstring '" + tv[0] + "': ";
this.require(1 === a.length, t + "array length is 1: " + a.length);
this.require(bitlen === tv[0].length, t + "length " + bitlen + " matches input length " + tv[0].length);
b = sjcl.bitArray.partial(tv[0].length, tv[1]);
this.require(a[0] === b, t + "array matches shifted number: " + word2hex(a[0]) + " == " + word2hex(b));
b = 0 | (a[0] >>> (32 - tv[0].length)); // unsigned shift, convert to signed word
this.require(b === (tv[1]|0), t + "array entry shifted is number: " + word2hex(b) + " == " + word2hex(tv[1]));
}
cb && cb();
});
new sjcl.test.TestCase("bitArray concat, slicing, shifting and clamping", function (cb) {
if (!sjcl.bitArray) {
this.unimplemented();
cb && cb();
return;
}
var i, j, kat = sjcl.test.vector.bitArray.slices, tv, a, a1, b, bitlen, t;
for (i=0; i<kat.length; i++) {
tv = kat[i];
a = [];
b = [];
bitlen = 0;
for (j=0; j<tv[0].length; j++) {
b[j] = concatbits(tv[0][j]);
a = sjcl.bitArray.concat(a, b[j]);
bitlen += tv[0][j].length;
}
// shift last array entry and set partial length on it
a1 = tv[1]; a1 = a1.slice(0, a1.length);
bitlen &= 31;
if (0 !== bitlen) a1[a1.length-1] = sjcl.bitArray.partial(bitlen, a1[a1.length-1]);
this.require(sjcl.bitArray.equal(a, a1), "concat: [" + a + "] == [" + a1 + "]");
t = 0;
for (j=0; j<tv[0].length; j++) {
bitlen = sjcl.bitArray.bitLength(b[j]);
this.require(bitlen === tv[0][j].length, "bitstring length");
a1 = sjcl.bitArray.bitSlice(a, t, t + bitlen);
this.require(sjcl.bitArray.equal(b[j], a1), "slice after concat: [" + b[j] + "] == [" + a1 + "]");
t += bitlen;
}
}
cb && cb();
});
new sjcl.test.TestCase("bitArray byteswap", function (cb) {
if (!sjcl.bitArray) {
this.unimplemented();
cb && cb();
return;
}
var i, kat = sjcl.test.vector.bitArray.byteswap, tv, a;
for (i=0; i<kat.length; i++) {
tv = kat[i];
a = tv[1];
this.require(sjcl.bitArray.equal(tv[0], sjcl.bitArray.byteswapM(a.slice(0, a.length))));
}
cb && cb();
});
})();

View File

@@ -0,0 +1,163 @@
sjcl.test.vector.bitArray = {};
// random test cases generated with ruby String#to_i(radix) and Fixnum#to_s(radix)
// bitstrings (<= 32 bits) encoding a number
sjcl.test.vector.bitArray.bits = [
[ "00" , 0|0x0 ],
[ "01" , 0|0x1 ],
[ "10" , 0|0x2 ],
[ "11" , 0|0x3 ],
[ "000" , 0|0x0 ],
[ "011" , 0|0x3 ],
[ "101" , 0|0x5 ],
[ "110" , 0|0x6 ],
[ "0100" , 0|0x4 ],
[ "0101" , 0|0x5 ],
[ "1000" , 0|0x8 ],
[ "1100" , 0|0xc ],
[ "00101" , 0|0x5 ],
[ "01010" , 0|0xa ],
[ "10011" , 0|0x13 ],
[ "11010" , 0|0x1a ],
[ "001100" , 0|0xc ],
[ "001110" , 0|0xe ],
[ "010100" , 0|0x14 ],
[ "100111" , 0|0x27 ],
[ "0001011" , 0|0xb ],
[ "0001101" , 0|0xd ],
[ "1000100" , 0|0x44 ],
[ "1101011" , 0|0x6b ],
[ "00100001" , 0|0x21 ],
[ "00100111" , 0|0x27 ],
[ "00101000" , 0|0x28 ],
[ "10101111" , 0|0xaf ],
[ "000100000" , 0|0x20 ],
[ "100100011" , 0|0x123 ],
[ "100111001" , 0|0x139 ],
[ "111010011" , 0|0x1d3 ],
[ "0001001011" , 0|0x4b ],
[ "0001100110" , 0|0x66 ],
[ "0010110111" , 0|0xb7 ],
[ "1011101111" , 0|0x2ef ],
[ "01000010110" , 0|0x216 ],
[ "01001101000" , 0|0x268 ],
[ "10111101001" , 0|0x5e9 ],
[ "11111100000" , 0|0x7e0 ],
[ "000101010001" , 0|0x151 ],
[ "010101111111" , 0|0x57f ],
[ "101010001110" , 0|0xa8e ],
[ "110101100010" , 0|0xd62 ],
[ "0010001111010" , 0|0x47a ],
[ "1000000001100" , 0|0x100c ],
[ "1100011000000" , 0|0x18c0 ],
[ "1110011000011" , 0|0x1cc3 ],
[ "00111101111110" , 0|0xf7e ],
[ "01101011001001" , 0|0x1ac9 ],
[ "10111000111101" , 0|0x2e3d ],
[ "11101010011110" , 0|0x3a9e ],
[ "010111101010110" , 0|0x2f56 ],
[ "100011010110000" , 0|0x46b0 ],
[ "110001001100100" , 0|0x6264 ],
[ "111011000100110" , 0|0x7626 ],
[ "0100101111001100" , 0|0x4bcc ],
[ "1000000001101100" , 0|0x806c ],
[ "1001000100110110" , 0|0x9136 ],
[ "1101010000100011" , 0|0xd423 ],
[ "01001001001101110" , 0|0x926e ],
[ "01111001111000010" , 0|0xf3c2 ],
[ "10011011011000011" , 0|0x136c3 ],
[ "10101011001110000" , 0|0x15670 ],
[ "010000000101000110" , 0|0x10146 ],
[ "011000100101110001" , 0|0x18971 ],
[ "101100100110110111" , 0|0x2c9b7 ],
[ "101110100100101111" , 0|0x2e92f ],
[ "0010101100101000000" , 0|0x15940 ],
[ "1011010010000101010" , 0|0x5a42a ],
[ "1011100111011011000" , 0|0x5ced8 ],
[ "1111011110011111110" , 0|0x7bcfe ],
[ "00101000011011111111" , 0|0x286ff ],
[ "01111001100011000100" , 0|0x798c4 ],
[ "11111001001110101011" , 0|0xf93ab ],
[ "11111001111001101001" , 0|0xf9e69 ],
[ "000110100000110010101" , 0|0x34195 ],
[ "011110000101101101111" , 0|0xf0b6f ],
[ "101111010011001100110" , 0|0x17a666 ],
[ "111101001011110010001" , 0|0x1e9791 ],
[ "1001111100011011100001" , 0|0x27c6e1 ],
[ "1011110101000101010110" , 0|0x2f5156 ],
[ "1100000100011110001011" , 0|0x30478b ],
[ "1100001010010110111111" , 0|0x30a5bf ],
[ "01001111001101000111101" , 0|0x279a3d ],
[ "10110011101111110000001" , 0|0x59df81 ],
[ "11000001101100110100011" , 0|0x60d9a3 ],
[ "11011000010110110010110" , 0|0x6c2d96 ],
[ "010011110100100110010100" , 0|0x4f4994 ],
[ "011101110001100111111110" , 0|0x7719fe ],
[ "011110001010011011100011" , 0|0x78a6e3 ],
[ "111100010001011101111110" , 0|0xf1177e ],
[ "0010110000010110001000010" , 0|0x582c42 ],
[ "0100100011001001101110000" , 0|0x919370 ],
[ "1000111001010110111010110" , 0|0x11cadd6 ],
[ "1111001100101000010010101" , 0|0x1e65095 ],
[ "00110010011100010101111111" , 0|0xc9c57f ],
[ "00111101011011010100111110" , 0|0xf5b53e ],
[ "01100000111011111010011100" , 0|0x183be9c ],
[ "11010001100110101111010111" , 0|0x3466bd7 ],
[ "010000001111100110000110011" , 0|0x207cc33 ],
[ "011010010000110000101011111" , 0|0x348615f ],
[ "011010110001110110001110010" , 0|0x358ec72 ],
[ "110110100001001001001110000" , 0|0x6d09270 ],
[ "0101000000000001000011100101" , 0|0x50010e5 ],
[ "0110011001001100011111111100" , 0|0x664c7fc ],
[ "1001011010000001100110111101" , 0|0x96819bd ],
[ "1011011101000000111000000010" , 0|0xb740e02 ],
[ "00100111111011011111010101101" , 0|0x4fdbead ],
[ "00110000011001110110101110010" , 0|0x60ced72 ],
[ "10111110111000010010010111101" , 0|0x17dc24bd ],
[ "11010001000001110010101000010" , 0|0x1a20e542 ],
[ "001001010001010111111101010111" , 0|0x9457f57 ],
[ "100110011101100000110111111010" , 0|0x26760dfa ],
[ "100111000100011001010011111011" , 0|0x271194fb ],
[ "111011110110101110110111010101" , 0|0x3bdaedd5 ],
[ "0111100111010100101010000111100" , 0|0x3cea543c ],
[ "1000010011010100111001110000100" , 0|0x426a7384 ],
[ "1001110000000100001011010001010" , 0|0x4e02168a ],
[ "1101000000000110110010011010100" , 0|0x680364d4 ],
[ "00000011100100001011101011100111", 0|0x390bae7 ],
[ "10011110110111111000010010010011", 0|0x9edf8493 ],
[ "11000101100000110001011010111100", 0|0xc58316bc ],
[ "11111010101110011001010001011000", 0|0xfab99458 ]
];
// concat some bitstrings into an array (the last array entry here is not "high" shifted yet)
sjcl.test.vector.bitArray.slices = [
// lengths: 17, 16, 15, 14, 13
[ ["00100010111010110", "0010000111100001", "101111111100010", "10111011110000", "1100100100001" ], [0|0x22eb10f0, 0|0xdfe2bbc3, 0|0x121] ],
[ ["00001010100110010", "0000100011110001", "111110101100100", "10110111000101", "0000100111011" ], [0|0x0a990478, 0|0xfd64b714, 0|0x13b] ],
[ ["10110101101100001", "0000110110010100", "110100011010100", "01011010010111", "0101101011011" ], [0|0xb5b086ca, 0|0x68d45a5d, 0|0x35b] ],
[ ["01010010001010110", "1110010000100111", "001010110101110", "10110111101000", "0100000010100" ], [0|0x522b7213, 0|0x95aeb7a1, 0|0x014] ],
[ ["10011001001100110", "0100100011100010", "000110011001001", "00101101101000", "0001111101100" ], [0|0x99332471, 0|0x0cc92da0, 0|0x3ec] ],
[ ["01111100000110000", "1000101001000010", "110101001111111", "01001010110110", "0111100000001" ], [0|0x7c184521, 0|0x6a7f4ad9, 0|0x701] ],
[ ["00110011010100110", "0100101101110000", "110101110100100", "00111010010110", "0110011011100" ], [0|0x335325b8, 0|0x6ba43a59, 0|0x4dc] ],
[ ["11111110011100100", "1111101001010011", "001000100110010", "01101101100001", "0001011101011" ], [0|0xfe727d29, 0|0x91326d84, 0|0x2eb] ],
[ ["00100011001001000", "1001010100000100", "010111001100111", "11011111000000", "0100111101110" ], [0|0x23244a82, 0|0x2e67df01, 0|0x1ee] ],
[ ["10111111100000010", "0111110011010001", "001000101001110", "11101010011001", "0101111101110" ], [0|0xbf813e68, 0|0x914eea65, 0|0x3ee] ],
// lengths: 17, 15, 32, 10, 20
[ ["00001111000110001", "110100011111001", "10010110001110010011011000011111", "1001101101", "01110010001111001110" ], [0|0x0f18e8f9, 0|0x9639361f, 0|0x26d723ce] ],
[ ["11011111010101100", "101010100101100", "11101101011001011011010111101011", "0010110010", "11110011110110000010" ], [0|0xdf56552c, 0|0xed65b5eb, 0|0x0b2f3d82] ],
[ ["10100100001000101", "100111001100011", "00111100101000110010000011101001", "1001110001", "11000000001111111110" ], [0|0xa422ce63, 0|0x3ca320e9, 0|0x271c03fe] ],
[ ["01110001111111010", "000101001101110", "00001100011010100100001011111001", "1011111010", "00011001110111111001" ], [0|0x71fd0a6e, 0|0x0c6a42f9, 0|0x2fa19df9] ],
[ ["01001100010011001", "000111011000100", "10100000110101110110011110001100", "0011000001", "11001101010101011100" ], [0|0x4c4c8ec4, 0|0xa0d7678c, 0|0x0c1cd55c] ],
[ ["01010010100110110", "110111110001000", "10011100001100010100000110000000", "1100111101", "00010001110100111101" ], [0|0x529b6f88, 0|0x9c314180, 0|0x33d11d3d] ],
[ ["11010100011101101", "110101011011010", "01000110000011001111101110100001", "0001001110", "01001100010010001010" ], [0|0xd476eada, 0|0x460cfba1, 0|0x04e4c48a] ],
[ ["11000001010001101", "001111011110111", "10000010100111010111011000100001", "0011000011", "11011110110000101000" ], [0|0xc1469ef7, 0|0x829d7621, 0|0x0c3dec28] ],
[ ["11000101100010100", "000110111111110", "11111011110010001100001100010000", "0000101010", "10000011010011100011" ], [0|0xc58a0dfe, 0|0xfbc8c310, 0|0x02a834e3] ],
[ ["11111111110100101", "110101000011001", "10101010010100111010100010110100", "0010011000", "10100110001000000010" ], [0|0xffd2ea19, 0|0xaa53a8b4, 0|0x098a6202] ]
];
sjcl.test.vector.bitArray.byteswap = [
[ [ 0xdab1a44e, 0x73fdc3de, 0xed6e6f00, 0x5d221b85, 0xadb987a4, 0xc20eda76, 0xa0d40d0c, 0xd1da9657, 0xd85eebf0, 0xd3f9c06d, 0xca1e39b0, 0xccd7eaeb, 0xb1dd6bd0, 0x7315a9e6, 0x77cfcac4, 0xf7914c19, 0x1bc15354, 0x935afabc, 0x3ee9d375, 0xd18a095f]
, [ 0x4ea4b1da, 0xdec3fd73, 0x006f6eed, 0x851b225d, 0xa487b9ad, 0x76da0ec2, 0x0c0dd4a0, 0x5796dad1, 0xf0eb5ed8, 0x6dc0f9d3, 0xb0391eca, 0xebead7cc, 0xd06bddb1, 0xe6a91573, 0xc4cacf77, 0x194c91f7, 0x5453c11b, 0xbcfa5a93, 0x75d3e93e, 0x5f098ad1]
]
];

View File

@@ -7,7 +7,7 @@ new sjcl.test.TestCase("ECC convenience test", function (cb) {
try { try {
var keys = sjcl.ecc.elGamal.generateKeys(192,0); var keys = sjcl.ecc.elGamal.generateKeys(192,0);
var ciphertext = sjcl.encrypt(keys.pub, "hello world"); var ciphertext = sjcl.encrypt(keys.pub, "hello world");
var plaintext = sjcl.decrypt(keys.sec, ciphertext); var plaintext = sjcl.decrypt(keys.sec, ciphertext);

View File

@@ -5,11 +5,23 @@ new sjcl.test.TestCase("HMAC official test vectors", function (cb) {
return; return;
} }
var i, kat = sjcl.test.vector.hmac, tv, h=sjcl.codec.hex, out; var i, kat = sjcl.test.vector.hmac, tv, h=sjcl.codec.hex, out, data, mac;
for (i=0; i<kat.length; i++) { for (i=0; i<kat.length; i++) {
tv = kat[i]; tv = kat[i];
out = h.fromBits((new sjcl.misc.hmac(h.toBits(tv.key))).mac(h.toBits(tv.data))); data = h.toBits(tv.data);
mac = new sjcl.misc.hmac(h.toBits(tv.key));
out = h.fromBits(mac.mac(data));
this.require (out.substr(0,tv.mac.length) == tv.mac, "hmac #"+i); this.require (out.substr(0,tv.mac.length) == tv.mac, "hmac #"+i);
out = h.fromBits(mac.mac(data));
this.require (out.substr(0,tv.mac.length) == tv.mac, "hmac reset #"+i);
mac.update(sjcl.bitArray.bitSlice(data, 0, sjcl.bitArray.bitLength(data)/2));
mac.update(sjcl.bitArray.bitSlice(data, sjcl.bitArray.bitLength(data)/2));
out = h.fromBits(mac.digest());
this.require (out.substr(0,tv.mac.length) == tv.mac, "hmac reset #"+i);
} }
cb && cb(); cb && cb();
}); });

View File

@@ -1,4 +1,5 @@
/* Official HMAC test vectors. */ /* Official HMAC test vectors. */
//Nilos: http://tools.ietf.org/html/draft-nystrom-smime-hmac-sha-02 for
sjcl.test.vector.hmac = [ sjcl.test.vector.hmac = [
{ key: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", { key: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
data: "4869205468657265", data: "4869205468657265",

View File

@@ -0,0 +1,126 @@
/*
* Asserts that an object can be encoded to an expected string.
*/
new sjcl.test.TestCase("JSON Encode Test", function (cb) {
if(!sjcl.json) {
this.unimplemented();
cb && cb();
return;
}
//Build up a standard object for encoding, this includes a nice wide variety of properties.
var obj = new Object();
obj.int = 4;
obj.nint = -5;
obj.str = 'string';
obj.iv = [ -95577995, -949876189, 1443400017, 697058741 ];
obj.truth = true;
obj.lie = false;
try {
var str = sjcl.json.encode(obj);
this.require(!(!str)); //Check for non-'falsey'
}
catch (e) {
//The standard object should encode just fine, so this is out of place. Fail.
this.fail(e);
}
cb && cb();
});
/*
* Asserts that a JSON string can be decoded to an expected object.
*/
new sjcl.test.TestCase("JSON Decode Test", function (cb) {
if(!sjcl.json) {
this.unimplemented();
cb && cb();
return;
}
var str = ''; var i;
str = '{"int":4,"nint":-5,"str":"string","iv":"/////wAAAAAAAAABAAAAAg==","truth":true,"lie":false}';
try {
var obj = sjcl.json.decode(str);
this.require(obj.int === 4);
this.require(obj.nint === -5);
this.require(obj.str === 'string');
this.require(obj.truth === true);
this.require(obj.lie === false);
for(i in obj.iv) {
this.require(obj.iv[i] == (i-1)); //Array in iv is [-1,0,1,2]
}
} catch (e) { this.fail(e); }
str = '{ "int" : 4, "nint" : -5,"str":"string", "iv": "/////wAAAAAAAAABAAAAAg==","truth": true,"lie": false }';
try {
var obj = sjcl.json.decode(str);
this.require(obj.int === 4);
this.require(obj.nint === -5);
this.require(obj.str === 'string');
this.require(obj.truth === true);
this.require(obj.lie === false);
for(i in obj.iv) {
this.require(obj.iv[i] == (i-1)); //Array in iv is [-1,0,1,2]
}
} catch (e) { this.fail(e); }
//Tests passed, return.
cb && cb();
});
/*
* Asserts that an Object can be Encoded to a string that can be decoded to an equivalent object
* as well as the converse.
*/
new sjcl.test.TestCase("JSON Commutative Test", function (cb) {
if(!sjcl.json) {
this.unimplemented();
cb && cb();
return;
}
var obj1 = new Object();
obj1.int = 4;
obj1.nint = -5;
obj1.str = 'string';
obj1.iv = [ -95577995, -949876189, 1443400017, 697058741 ];
obj1.truth = true;
obj1.lie = false;
var str1 = '';
var str2 = '';
var obj2;
try {
str1 = sjcl.json.encode(obj1);
obj2 = sjcl.json.decode(str1);
str2 = sjcl.json.encode(obj2);
}
catch (e) {
this.fail(e);
}
try {
this.require(str1 === str2);
this.require(obj1.int == obj2.int);
this.require(obj1.str == obj2.str);
this.require(obj1.lie == obj2.lie);
this.require(obj1.nint == obj2.nint);
this.require(obj1.truth == obj2.truth);
var i;
for(i in obj1.iv)
{
this.require(obj1.iv[i] == obj2.iv[i]);
}
}
catch (e) {
this.fail(e);
}
//Tests passed.
cb && cb();
});

View File

@@ -0,0 +1,16 @@
var fs = require('fs');
var vm = require('vm');
var load = function(path) {
vm.runInThisContext(fs.readFileSync(path));
};
// Assume we're run using `make test`.
// That means argv[0] is `node` and argv[1] is this file.
process.argv.slice(2).map(load);
sjcl.test.run(undefined, function(){
if(!browserUtil.allPassed) {
process.exit(1);
}
});

View File

@@ -29,13 +29,15 @@ describe('Account', function(){
}); });
// XXX: clean up the stubbed out remote methods
describe('#publicKeyIsActive()', function(){ describe('#publicKeyIsActive()', function(){
it('should respond true if the public key corresponds to the account address and the master key IS NOT disabled', 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({ var account = new Account({
on: function(){}, on: function(){},
request_account_info: function(address, callback) { requestAccountInfo: function(address, callback) {
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
callback(null, { account_data: { callback(null, { account_data: {
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
@@ -56,7 +58,7 @@ describe('Account', function(){
var account = new Account({ var account = new Account({
on: function(){}, on: function(){},
request_account_info: function(address, callback) { requestAccountInfo: function(address, callback) {
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
callback(null, { account_data: { callback(null, { account_data: {
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
@@ -77,7 +79,7 @@ describe('Account', function(){
var account = new Account({ var account = new Account({
on: function(){}, on: function(){},
request_account_info: function(address, callback) { requestAccountInfo: function(address, callback) {
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
callback(null, { account_data: { callback(null, { account_data: {
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
@@ -99,7 +101,7 @@ describe('Account', function(){
var account = new Account({ var account = new Account({
on: function(){}, on: function(){},
request_account_info: function(address, callback) { requestAccountInfo: function(address, callback) {
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
callback(null, { account_data: { callback(null, { account_data: {
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
@@ -121,7 +123,7 @@ describe('Account', function(){
var account = new Account({ var account = new Account({
on: function(){}, on: function(){},
request_account_info: function(address, callback) { requestAccountInfo: function(address, callback) {
if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { if (address === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') {
callback(null, { account_data: { callback(null, { account_data: {
Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz',
@@ -142,7 +144,7 @@ describe('Account', function(){
var account = new Account({ var account = new Account({
on: function(){}, on: function(){},
request_account_info: function(address, callback) { requestAccountInfo: function(address, callback) {
if (address === 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ') { if (address === 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ') {
callback({ error: 'remoteError', callback({ error: 'remoteError',
error_message: 'Remote reported an error.', error_message: 'Remote reported an error.',

View File

@@ -16,83 +16,256 @@ describe('Amount', function() {
assert(Amount.from_json('1').is_positive()); assert(Amount.from_json('1').is_positive());
}); });
}); });
describe('Positives', function() {
it('Number 1', function() {
assert(Amount.from_json('1').is_positive());
});
});
// also tested extensively in other cases
describe('to_human', function() {
it('12345.6789 XAU', function() {
assert.strictEqual(Amount.from_human("12345.6789 XAU").to_human(), '12,345.6789');
});
it('12345.678901234 XAU', function() {
assert.strictEqual(Amount.from_human("12345.678901234 XAU").to_human(), '12,345.678901234');
});
it('to human, precision -1, should be ignored, precision needs to be >= 0', function() {
assert.strictEqual(Amount.from_human("12345.678901234 XAU").to_human({precision:-1}), '12,346');
});
it('to human, precision 0', function() {
assert.strictEqual(Amount.from_human("12345.678901234 XAU").to_human({precision:0}), '12,346');
});
it('to human, precision 1', function() {
assert.strictEqual(Amount.from_human("12345.678901234 XAU").to_human({precision:1}), '12,345.7');
});
it('to human, precision 2', function() {
assert.strictEqual(Amount.from_human("12345.678901234 XAU").to_human({precision:2}), '12,345.68');
});
it('to human, precision 3', function() {
assert.strictEqual(Amount.from_human("12345.678901234 XAU").to_human({precision:3}), '12,345.679');
});
it('to human, precision 4', function() {
assert.strictEqual(Amount.from_human("12345.678901234 XAU").to_human({precision:4}), '12,345.6789');
});
it('to human, precision 5', function() {
assert.strictEqual(Amount.from_human("12345.678901234 XAU").to_human({precision:5}), '12,345.67890');
});
it('to human, precision -1, should be ignored, precision needs to be >= 0', function() {
assert.strictEqual(Amount.from_human("0.00012345 XAU").to_human({precision:-1}), '0');
});
it('to human, precision 0', function() {
assert.strictEqual(Amount.from_human("0.00012345 XAU").to_human({precision:0}), '0');
});
it('to human, precision 1', function() {
assert.strictEqual(Amount.from_human("0.00012345 XAU").to_human({precision:1}), '0.0');
});
it('to human, precision 2', function() {
assert.strictEqual(Amount.from_human("0.00012345 XAU").to_human({precision:2}), '0.00');
});
it('to human, precision 5', function() {
assert.strictEqual(Amount.from_human("0.00012345 XAU").to_human({precision:5}), '0.00012');
});
it('to human, precision 6', function() {
assert.strictEqual(Amount.from_human("0.00012345 XAU").to_human({precision:6}), '0.000123');
});
it('to human, precision 16', function() {
assert.strictEqual(Amount.from_human("0.00012345 XAU").to_human({precision:16}), '0.00012345');
});
it('to human, precision 16, min_precision 16', function() {
assert.strictEqual(Amount.from_human("0.00012345 XAU").to_human({precision:16, min_precision:16}), '0.0001234500000000');
});
it('to human, precision 16, min_precision 12', function() {
assert.strictEqual(Amount.from_human("0.00012345 XAU").to_human({precision:16, min_precision:12}), '0.000123450000');
});
it('to human, precision 0, first decimal 4', function() {
assert.strictEqual(Amount.from_human("0.4 XAU").to_human({precision:0}), '0');
});
it('to human, precision 0, first decimal 5', function() {
assert.strictEqual(Amount.from_human("0.5 XAU").to_human({precision:0}), '1');
});
it('to human, precision 0, first decimal 8', function() {
assert.strictEqual(Amount.from_human("0.8 XAU").to_human({precision:0}), '1');
});
it('to human, precision 0, precision 16', function() {
assert.strictEqual(Amount.from_human("0.0 XAU").to_human({precision:16}), '0');
});
it('to human, precision 0, precision 8, min_precision 16', function() {
assert.strictEqual(Amount.from_human("0.0 XAU").to_human({precision:8, min_precision:16}), '0.0000000000000000');
});
it('to human, precision 0, first decimal 8', function() {
assert.strictEqual(Amount.from_human("0.8 XAU").to_human({precision:0}), '1');
});
it('to human, precision 6, min_precision 6, max_sig_digits 20', function() {
assert.strictEqual(Amount.from_human("0.0 XAU").to_human({precision: 6, min_precision: 6, max_sig_digits: 20}), '0.000000');
});
it('to human, precision 16, min_precision 6, max_sig_digits 20', function() {
assert.strictEqual(Amount.from_human("0.0 XAU").to_human({precision: 16, min_precision: 6, max_sig_digits: 20}), '0.000000');
});
it('to human rounding edge case, precision 2, 1', function() {
assert.strictEqual(Amount.from_human("0.99 XAU").to_human({precision:1}), '1.0');
});
it('to human rounding edge case, precision 2, 2', function() {
assert.strictEqual(Amount.from_human("0.99 XAU").to_human({precision:2}), '0.99');
});
it('to human rounding edge case, precision 2, 3', function() {
assert.strictEqual(Amount.from_human("0.99 XAU").to_human({precision:3}), '0.99');
});
it('to human rounding edge case, precision 2, 3 min precision 3', function() {
assert.strictEqual(Amount.from_human("0.99 XAU").to_human({precision:3, min_precision:3}), '0.990');
});
it('to human rounding edge case, precision 3, 2', function() {
assert.strictEqual(Amount.from_human("0.999 XAU").to_human({precision:2}), '1.00');
});
});
describe('from_human', function() { describe('from_human', function() {
it('1 XRP', function() { it('1 XRP', function() {
assert.strictEqual(Amount.from_human("1 XRP").to_text_full(), '1/XRP'); assert.strictEqual(Amount.from_human("1 XRP").to_text_full(), '1/XRP');
}); });
it('1 XRP human', function() {
assert.strictEqual(Amount.from_human("1 XRP").to_human_full(), '1/XRP');
});
it('0.1 XRP', function() { it('0.1 XRP', function() {
assert.strictEqual(Amount.from_human("0.1 XRP").to_text_full(), '0.1/XRP'); assert.strictEqual(Amount.from_human("0.1 XRP").to_text_full(), '0.1/XRP');
}); });
it('0.1 XRP human', function() {
assert.strictEqual(Amount.from_human("0.1 XRP").to_human_full(), '0.1/XRP');
});
it('0.1 USD', function() { it('0.1 USD', function() {
assert.strictEqual(Amount.from_human("0.1 USD").to_text_full(), '0.1/USD/NaN'); assert.strictEqual(Amount.from_human("0.1 USD").to_text_full(), '0.1/USD/NaN');
}); });
it('0.1 USD human', function() {
assert.strictEqual(Amount.from_human("0.1 USD").to_human_full(), '0.1/USD/NaN');
});
it('10000 USD', function() { it('10000 USD', function() {
assert.strictEqual(Amount.from_human("10000 USD").to_text_full(), '10000/USD/NaN'); assert.strictEqual(Amount.from_human("10000 USD").to_text_full(), '10000/USD/NaN');
}); });
it('10000 USD human', function() {
assert.strictEqual(Amount.from_human("10000 USD").to_human_full(), '10,000/USD/NaN');
});
it('USD 10000', function() { it('USD 10000', function() {
assert.strictEqual(Amount.from_human("USD 10000").to_text_full(), '10000/USD/NaN'); assert.strictEqual(Amount.from_human("USD 10000").to_text_full(), '10000/USD/NaN');
}); });
it('USD 10000 human', function() {
assert.strictEqual(Amount.from_human("USD 10000").to_human_full(), '10,000/USD/NaN');
});
it('12345.6789 XAU', function() { it('12345.6789 XAU', function() {
assert.strictEqual(Amount.from_human("12345.6789 XAU").to_text_full(), '12345.6789/XAU/NaN'); assert.strictEqual(Amount.from_human("12345.6789 XAU").to_text_full(), '12345.6789/XAU/NaN');
}); });
it('12345.6789 XAU human', function() {
assert.strictEqual(Amount.from_human("12345.6789 XAU").to_human_full(), '12,345.6789/XAU/NaN');
});
it('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000', function() { it('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000', function() {
assert.strictEqual(Amount.from_human("12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000").to_text_full(), '12345.6789/XAU (-0.5%pa)/NaN'); assert.strictEqual(Amount.from_human("12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000").to_text_full(), '12345.6789/XAU (-0.5%pa)/NaN');
}); });
it('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000 human', function() {
assert.strictEqual(Amount.from_human("12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human_full(), '12,345.6789/XAU (-0.5%pa)/NaN');
});
it('12345.6789 0000000000000000000000005553440000000000', function() { it('12345.6789 0000000000000000000000005553440000000000', function() {
assert.strictEqual(Amount.from_human("12345.6789 0000000000000000000000005553440000000000").to_text_full(), '12345.6789/USD/NaN'); assert.strictEqual(Amount.from_human("12345.6789 0000000000000000000000005553440000000000").to_text_full(), '12345.6789/USD/NaN');
}); });
it('12345.6789 0000000000000000000000005553440000000000 human', function() {
assert.strictEqual(Amount.from_human("12345.6789 0000000000000000000000005553440000000000").to_human_full(), '12,345.6789/USD/NaN');
});
it('10 0000000000000000000000005553440000000000', function() { it('10 0000000000000000000000005553440000000000', function() {
assert.strictEqual(Amount.from_human("10 0000000000000000000000005553440000000000").to_text_full(), '10/USD/NaN'); assert.strictEqual(Amount.from_human("10 0000000000000000000000005553440000000000").to_text_full(), '10/USD/NaN');
}); });
it('10 0000000000000000000000005553440000000000 human', function() {
assert.strictEqual(Amount.from_human("10 0000000000000000000000005553440000000000").to_human_full(), '10/USD/NaN');
});
it('100 0000000000000000000000005553440000000000', function() { it('100 0000000000000000000000005553440000000000', function() {
assert.strictEqual(Amount.from_human("100 0000000000000000000000005553440000000000").to_text_full(), '100/USD/NaN'); assert.strictEqual(Amount.from_human("100 0000000000000000000000005553440000000000").to_text_full(), '100/USD/NaN');
}); });
it('100 0000000000000000000000005553440000000000 human', function() {
assert.strictEqual(Amount.from_human("100 0000000000000000000000005553440000000000").to_human_full(), '100/USD/NaN');
});
it('1000 0000000000000000000000005553440000000000', function() { it('1000 0000000000000000000000005553440000000000', function() {
assert.strictEqual(Amount.from_human("1000 0000000000000000000000005553440000000000").to_text_full(), '1000/USD/NaN'); assert.strictEqual(Amount.from_human("1000 0000000000000000000000005553440000000000").to_text_full(), '1000/USD/NaN');
}); });
it('1000 0000000000000000000000005553440000000000 human', function() {
assert.strictEqual(Amount.from_human("1000 0000000000000000000000005553440000000000").to_human_full(), '1,000/USD/NaN');
});
it('-100 0000000000000000000000005553440000000000', function() { it('-100 0000000000000000000000005553440000000000', function() {
assert.strictEqual(Amount.from_human("-100 0000000000000000000000005553440000000000").to_text_full(), '-100/USD/NaN'); assert.strictEqual(Amount.from_human("-100 0000000000000000000000005553440000000000").to_text_full(), '-100/USD/NaN');
}); });
it('-100 0000000000000000000000005553440000000000 human', function() {
assert.strictEqual(Amount.from_human("-100 0000000000000000000000005553440000000000").to_human_full(), '-100/USD/NaN');
});
it('-1000 0000000000000000000000005553440000000000', function() { it('-1000 0000000000000000000000005553440000000000', function() {
assert.strictEqual(Amount.from_human("-1000 0000000000000000000000005553440000000000").to_text_full(), '-1000/USD/NaN'); assert.strictEqual(Amount.from_human("-1000 0000000000000000000000005553440000000000").to_text_full(), '-1000/USD/NaN');
}); });
it('-1000 0000000000000000000000005553440000000000 human', function() {
assert.strictEqual(Amount.from_human("-1000 0000000000000000000000005553440000000000").to_human_full(), '-1,000/USD/NaN');
});
it('-1000.001 0000000000000000000000005553440000000000', function() { it('-1000.001 0000000000000000000000005553440000000000', function() {
assert.strictEqual(Amount.from_human("-1000.001 0000000000000000000000005553440000000000").to_text_full(), '-1000.001/USD/NaN'); assert.strictEqual(Amount.from_human("-1000.001 0000000000000000000000005553440000000000").to_text_full(), '-1000.001/USD/NaN');
}); });
it('-1000.001 0000000000000000000000005553440000000000 human', function() {
assert.strictEqual(Amount.from_human("-1000.001 0000000000000000000000005553440000000000").to_human_full(), '-1,000.001/USD/NaN');
});
it('XAU 12345.6789', function() { it('XAU 12345.6789', function() {
assert.strictEqual(Amount.from_human("XAU 12345.6789").to_text_full(), '12345.6789/XAU/NaN'); assert.strictEqual(Amount.from_human("XAU 12345.6789").to_text_full(), '12345.6789/XAU/NaN');
}); });
it('XAU 12345.6789 human', function() {
assert.strictEqual(Amount.from_human("XAU 12345.6789").to_human_full(), '12,345.6789/XAU/NaN');
});
it('101 12345.6789', function() { it('101 12345.6789', function() {
assert.strictEqual(Amount.from_human("101 12345.6789").to_text_full(), '12345.6789/101/NaN'); assert.strictEqual(Amount.from_human("101 12345.6789").to_text_full(), '12345.6789/101/NaN');
}); });
it('101 12345.6789 human', function() {
assert.strictEqual(Amount.from_human("101 12345.6789").to_human_full(), '12,345.6789/101/NaN');
});
it('12345.6789 101', function() { it('12345.6789 101', function() {
assert.strictEqual(Amount.from_human("12345.6789 101").to_text_full(), '12345.6789/101/NaN'); assert.strictEqual(Amount.from_human("12345.6789 101").to_text_full(), '12345.6789/101/NaN');
}); });
it('12345.6789 101 human', function() {
assert.strictEqual(Amount.from_human("12345.6789 101").to_human_full(), '12,345.6789/101/NaN');
});
}); });
describe('from_json', function() { describe('from_json', function() {
it('1 XRP', function() { it('1 XRP', function() {
assert.strictEqual(Amount.from_json("1/XRP").to_text_full(), "1/XRP/NaN"); assert.strictEqual(Amount.from_json("1/XRP").to_text_full(), "1/XRP/NaN");
}); });
it('1 XRP human', function() {
assert.strictEqual(Amount.from_json("1/XRP").to_human_full(), "1/XRP/NaN");
});
}); });
describe('from_number', function() { describe('from_number', function() {
it('Number 1', function() { it('Number 1', function() {
assert.strictEqual(Amount.from_number(1).to_text_full(), '1/1/rrrrrrrrrrrrrrrrrrrrBZbvji'); assert.strictEqual(Amount.from_number(1).to_text_full(), '1/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
}); });
it('Number 1 human', function() {
assert.strictEqual(Amount.from_number(1).to_human_full(), '1/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
it('Number 2', function() { it('Number 2', function() {
assert.strictEqual(Amount.from_number(2).to_text_full(), '2/1/rrrrrrrrrrrrrrrrrrrrBZbvji'); assert.strictEqual(Amount.from_number(2).to_text_full(), '2/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
}); });
it('Number 2 human', function() {
assert.strictEqual(Amount.from_number(2).to_human_full(), '2/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
it('Multiply 2 "1" with 3 "1", by product_human', function () { it('Multiply 2 "1" with 3 "1", by product_human', function () {
assert.strictEqual(Amount.from_number(2).product_human(Amount.from_number(3)).to_text_full(), '6/1/rrrrrrrrrrrrrrrrrrrrBZbvji'); assert.strictEqual(Amount.from_number(2).product_human(Amount.from_number(3)).to_text_full(), '6/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
}); });
it('Multiply 2 "1" with 3 "1", by product_human human', function () {
assert.strictEqual(Amount.from_number(2).product_human(Amount.from_number(3)).to_human_full(), '6/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
it('Multiply 3 USD with 3 "1"', function () { it('Multiply 3 USD with 3 "1"', function () {
assert.strictEqual(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_number(3)).to_text_full(), '9/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); assert.strictEqual(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_number(3)).to_text_full(), '9/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
}); });
it('Multiply 3 USD with 3 "1" human', function () {
assert.strictEqual(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_number(3)).to_human_full(), '9/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('Multiply -1 "1" with 3 USD', function () { it('Multiply -1 "1" with 3 USD', function () {
assert.strictEqual(Amount.from_number(-1).multiply(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji'); assert.strictEqual(Amount.from_number(-1).multiply(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
}); });
it('Multiply -1 "1" with 3 USD human', function () {
assert.strictEqual(Amount.from_number(-1).multiply(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
it('Multiply -1 "1" with 3 USD, by product_human', function () { it('Multiply -1 "1" with 3 USD, by product_human', function () {
assert.strictEqual(Amount.from_number(-1).product_human(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji'); assert.strictEqual(Amount.from_number(-1).product_human(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
}); });
it('Multiply -1 "1" with 3 USD, by product_human human', function () {
assert.strictEqual(Amount.from_number(-1).product_human(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
}); });
describe('text_full_rewrite', function() { describe('text_full_rewrite', function() {
it('Number 1', function() { it('Number 1', function() {
@@ -162,12 +335,21 @@ describe('Amount', function() {
it('parse dem', function() { it('parse dem', function() {
assert.strictEqual(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); assert.strictEqual(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
}); });
it('parse dem human', function() {
assert.strictEqual(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('parse dem', function() { it('parse dem', function() {
assert.strictEqual(Amount.from_json('10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); assert.strictEqual(Amount.from_json('10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
}); });
it('parse dem human', function() {
assert.strictEqual(Amount.from_json('10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('Parse 800/USD/mtgox', function () { it('Parse 800/USD/mtgox', function () {
assert.strictEqual('800/USD/'+config.accounts['mtgox'].account, Amount.from_json('800/USD/mtgox').to_text_full()); assert.strictEqual('800/USD/'+config.accounts['mtgox'].account, Amount.from_json('800/USD/mtgox').to_text_full());
}); });
it('Parse 800/USD/mtgox human', function () {
assert.strictEqual('800/USD/'+config.accounts['mtgox'].account, Amount.from_json('800/USD/mtgox').to_human_full());
});
it('Parse native 0', function () { it('Parse native 0', function () {
assert.strictEqual('0/XRP', Amount.from_json('0').to_text_full()); assert.strictEqual('0/XRP', Amount.from_json('0').to_text_full());
}); });
@@ -211,7 +393,52 @@ describe('Amount', function() {
assert.strictEqual('0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full()); assert.strictEqual('0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full());
}); });
it('Parse 0.0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () { it('Parse 0.0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () {
assert.strictEqual('0/XRP/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full()); assert.strictEqual('0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full());
});
it('Parse native 0 human', function () {
assert.strictEqual('0/XRP', Amount.from_json('0').to_human_full());
});
it('Parse native 0.0 human', function () {
assert.strictEqual('0/XRP', Amount.from_json('0.0').to_human_full());
});
it('Parse native -0 human', function () {
assert.strictEqual('0/XRP', Amount.from_json('-0').to_human_full());
});
it('Parse native -0.0 human', function () {
assert.strictEqual('0/XRP', Amount.from_json('-0.0').to_human_full());
});
it('Parse native 1000 human', function () {
assert.strictEqual('0.001/XRP', Amount.from_json('1000').to_human_full());
});
it('Parse native 12.3 human', function () {
assert.strictEqual('12.3/XRP', Amount.from_json('12.3').to_human_full());
});
it('Parse native -12.3 human', function () {
assert.strictEqual('-12.3/XRP', Amount.from_json('-12.3').to_human_full());
});
it('Parse 123./USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function () {
assert.strictEqual('123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('123./USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full());
});
it('Parse 12300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function () {
assert.strictEqual('12,300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('12300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full());
});
it('Parse 12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function () {
assert.strictEqual('12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full());
});
it('Parse 1.2300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function () {
assert.strictEqual('1.23/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('1.2300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full());
});
it('Parse -0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function () {
assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full());
});
it('Parse -0.0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function () {
assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-0.0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full());
});
it('Parse 0.0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function () {
assert.strictEqual('0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full());
});
it('Parse 0.0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function () {
assert.strictEqual('0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full());
}); });
}); });
describe('Amount to_json', function() { describe('Amount to_json', function() {
@@ -370,6 +597,144 @@ describe('Amount', function() {
it('Divide EUR by XRP, neg, <1', function () { it('Divide EUR by XRP, neg, <1', function () {
assert.strictEqual('-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000')).to_text_full()); assert.strictEqual('-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000')).to_text_full());
}); });
it('Negate native 123 human', function () {
assert.strictEqual('-0.000123/XRP', Amount.from_json('123').negate().to_human_full());
});
it('Negate native -123 human', function () {
assert.strictEqual('0.000123/XRP', Amount.from_json('-123').negate().to_human_full());
});
it('Negate non-native 123 human', function () {
assert.strictEqual('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').negate().to_human_full());
});
it('Negate non-native -123 human', function () {
assert.strictEqual('123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').negate().to_human_full());
});
it('Clone non-native -123 human', function () {
assert.strictEqual('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').clone().to_human_full());
});
it('Add XRP to XRP human', function () {
assert.strictEqual('0.0002/XRP', Amount.from_json('150').add(Amount.from_json('50')).to_human_full());
});
it('Add USD to USD human', function () {
assert.strictEqual('200.52/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('150.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').add(Amount.from_json('50.5/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Add 0 USD to 1 USD human', function() {
assert.strictEqual('1' , Amount.from_json('1/USD').add('0/USD').to_human());
});
it('Subtract USD from USD human', function() {
assert.strictEqual('99.52/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('150.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').subtract(Amount.from_json('50.5/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply 0 XRP with 0 XRP human', function () {
assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('0')).to_human_full());
});
it('Multiply 0 USD with 0 XRP human', function () {
assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('0')).to_human_full());
});
it('Multiply 0 XRP with 0 USD human', function () {
assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply 1 XRP with 0 XRP human', function () {
assert.strictEqual('0/XRP', Amount.from_json('1').multiply(Amount.from_json('0')).to_human_full());
});
it('Multiply 1 USD with 0 XRP human', function () {
assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('0')).to_human_full());
});
it('Multiply 1 XRP with 0 USD human', function () {
assert.strictEqual('0/XRP', Amount.from_json('1').multiply(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply 0 XRP with 1 XRP human', function () {
assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('1')).to_human_full());
});
it('Multiply 0 USD with 1 XRP human', function () {
assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('1')).to_human_full());
});
it('Multiply 0 XRP with 1 USD human', function () {
assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply XRP with USD human', function () {
assert.equal('0.002/XRP', Amount.from_json('200').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply XRP with USD human', function () {
assert.strictEqual('0.2/XRP', Amount.from_json('20000').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply XRP with USD human', function () {
assert.strictEqual('20/XRP', Amount.from_json('2000000').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply XRP with USD, neg human', function () {
assert.strictEqual('-0.002/XRP', Amount.from_json('200').multiply(Amount.from_json('-10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply XRP with USD, neg, frac human', function () {
assert.strictEqual('-0.222/XRP', Amount.from_json('-6000').multiply(Amount.from_json('37/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply USD with USD human', function () {
assert.strictEqual('20,000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply USD with USD human', function () {
assert.strictEqual('200,000,000,000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('100000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply EUR with USD, result < 1 human', function () {
assert.strictEqual('100,000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply EUR with USD, neg human', function () {
assert.strictEqual('-48,000,000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-24000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply EUR with USD, neg, <1 human', function () {
assert.strictEqual('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('-1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply EUR with XRP, factor < 1 human', function () {
assert.strictEqual('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('2000')).to_human_full());
});
it('Multiply EUR with XRP, neg human', function () {
assert.strictEqual('-500/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('5')).to_human_full());
});
it('Multiply EUR with XRP, neg, <1 human', function () {
assert.strictEqual('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('2000')).to_human_full());
});
it('Multiply XRP with XRP human', function () {
assert.strictEqual('0.0001/XRP', Amount.from_json('10').multiply(Amount.from_json('10')).to_human_full());
});
it('Divide XRP by USD human', function () {
assert.strictEqual('0.00002/XRP', Amount.from_json('200').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Divide XRP by USD human', function () {
assert.strictEqual('0.002/XRP', Amount.from_json('20000').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Divide XRP by USD human', function () {
assert.strictEqual('0.2/XRP', Amount.from_json('2000000').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Divide XRP by USD, neg human', function () {
assert.strictEqual('-0.00002/XRP', Amount.from_json('200').divide(Amount.from_json('-10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Divide XRP by USD, neg, frac human', function () {
assert.strictEqual('-0.000162/XRP', Amount.from_json('-6000').divide(Amount.from_json('37/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Divide USD by USD human', function () {
assert.strictEqual('200/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Divide USD by USD, fractional human', function () {
assert.strictEqual('57,142.85714285714/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('35/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Divide USD by USD human', function () {
assert.strictEqual('20/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('100000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Divide EUR by USD, factor < 1 human', function () {
assert.strictEqual('0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Divide EUR by USD, neg human', function () {
assert.strictEqual('-12/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-24000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Divide EUR by USD, neg, <1 human', function () {
assert.strictEqual('-0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('-1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Divide EUR by XRP, result < 1 human', function () {
assert.strictEqual('0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000')).to_human_full());
});
it('Divide EUR by XRP, neg human', function () {
assert.strictEqual('-20/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('5')).to_human_full());
});
it('Divide EUR by XRP, neg, <1 human', function () {
assert.strictEqual('-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000')).to_human_full());
});
it('Divide by zero should throw', function() { it('Divide by zero should throw', function() {
assert.throws(function() { assert.throws(function() {
Amount.from_json(1).divide(Amount.from_json(0)); Amount.from_json(1).divide(Amount.from_json(0));
@@ -607,12 +972,87 @@ describe('Amount', function() {
it('Multiply USD with XAU (dem)', function () { it('Multiply USD with XAU (dem)', function () {
assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_text_full(), '19900.00316303882/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_text_full(), '19900.00316303882/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
}); });
it('Multiply 0 XRP with 0 XRP human', function () {
assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('0')).to_human_full());
});
it('Multiply 0 USD with 0 XRP human', function () {
assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('0')).to_human_full());
});
it('Multiply 0 XRP with 0 USD human', function () {
assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply 1 XRP with 0 XRP human', function () {
assert.strictEqual('0/XRP', Amount.from_json('1').product_human(Amount.from_json('0')).to_human_full());
});
it('Multiply 1 USD with 0 XRP human', function () {
assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('0')).to_human_full());
});
it('Multiply 1 XRP with 0 USD human', function () {
assert.strictEqual('0/XRP', Amount.from_json('1').product_human(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply 0 XRP with 1 XRP human', function () {
assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('1')).to_human_full());
});
it('Multiply 0 USD with 1 XRP human', function () {
assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('1')).to_human_full());
});
it('Multiply 0 XRP with 1 USD human', function () {
assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply XRP with USD human', function () {
assert.equal('0.002/XRP', Amount.from_json('200').product_human(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply XRP with USD human', function () {
assert.strictEqual('0.2/XRP', Amount.from_json('20000').product_human(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply XRP with USD human', function () {
assert.strictEqual('20/XRP', Amount.from_json('2000000').product_human(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply XRP with USD, neg human', function () {
assert.strictEqual('-0.002/XRP', Amount.from_json('200').product_human(Amount.from_json('-10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply XRP with USD, neg, frac human', function () {
assert.strictEqual('-0.222/XRP', Amount.from_json('-6000').product_human(Amount.from_json('37/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply USD with USD human', function () {
assert.strictEqual('20,000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply USD with USD human', function () {
assert.strictEqual('200,000,000,000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('100000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply EUR with USD, result < 1 human', function () {
assert.strictEqual('100,000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full());
});
it('Multiply EUR with USD, neg human', function () {
assert.strictEqual(Amount.from_json('-24000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-48,000,000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('Multiply EUR with USD, neg, <1 human', function () {
assert.strictEqual(Amount.from_json('0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('-1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('Multiply EUR with XRP, factor < 1 human', function () {
assert.strictEqual(Amount.from_json('0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('2000')).to_human_full(), '0.0001/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('Multiply EUR with XRP, neg human', function () {
assert.strictEqual(Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('5')).to_human_full(), '-0.0005/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('Multiply EUR with XRP, neg, <1 human', function () {
assert.strictEqual(Amount.from_json('-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('2000')).to_human_full(), '-0.0001/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('Multiply XRP with XRP human', function () {
assert.strictEqual(Amount.from_json('10000000').product_human(Amount.from_json('10')).to_human_full(), '0.0001/XRP');
});
it('Multiply USD with XAU (dem) human', function () {
assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_human_full(), '19,900.00316303882/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
}); });
describe('ratio_human', function() { describe('ratio_human', function() {
it('Divide USD by XAU (dem)', function () { it('Divide USD by XAU (dem)', function () {
assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').ratio_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_text_full(), '201.0049931765529/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').ratio_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_text_full(), '201.0049931765529/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
}); });
it('Divide USD by XAU (dem) human', function () {
assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').ratio_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_human_full(), '201.0049931765529/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
}); });
describe('_invert', function() { describe('_invert', function() {
@@ -625,6 +1065,15 @@ describe('Amount', function() {
it('Invert 0.02', function () { it('Invert 0.02', function () {
assert.strictEqual(Amount.from_json('0.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').invert().to_text_full(), '50/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); assert.strictEqual(Amount.from_json('0.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').invert().to_text_full(), '50/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
}); });
it('Invert 1 human', function () {
assert.strictEqual(Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').invert().to_human_full(), '1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('Invert 20 human', function () {
assert.strictEqual(Amount.from_json('20/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').invert().to_human_full(), '0.05/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('Invert 0.02 human', function () {
assert.strictEqual(Amount.from_json('0.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').invert().to_human_full(), '50/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
}); });
describe('from_quality', function() { describe('from_quality', function() {
@@ -658,5 +1107,150 @@ describe('Amount', function() {
it('USD/XAU(dem) inverse', function () { it('USD/XAU(dem) inverse', function () {
assert.strictEqual(Amount.from_quality('CDFD3AFB2F8C5DBEF75B081F7C957FF5509563266F28F36C5704A0FB0BAD8800', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {inverse: true, base_currency: 'USD', reference_date: 443845330 + 31535000}).to_text_full(), '0.007675186123263489/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN'); assert.strictEqual(Amount.from_quality('CDFD3AFB2F8C5DBEF75B081F7C957FF5509563266F28F36C5704A0FB0BAD8800', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {inverse: true, base_currency: 'USD', reference_date: 443845330 + 31535000}).to_text_full(), '0.007675186123263489/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN');
}); });
it('BTC/XRP human', function () {
assert.strictEqual(Amount.from_quality('7B73A610A009249B0CC0D4311E8BA7927B5A34D86634581C5F0FF9FF678E1000', 'XRP', NaN, {base_currency: 'BTC'}).to_human_full(), '44,970/XRP');
});
it('BTC/XRP inverse human', function () {
assert.strictEqual(Amount.from_quality('37AAC93D336021AE94310D0430FFA090F7137C97D473488C4A0918D0DEF8624E', 'XRP', NaN, {inverse: true, base_currency: 'BTC'}).to_human_full(), '39,053.954453/XRP');
});
it('XRP/USD human', function () {
assert.strictEqual(Amount.from_quality('DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4D05DCAA8FE12000', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {base_currency: 'XRP'}).to_human_full(), '0.0165/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B');
});
it('XRP/USD inverse human', function () {
assert.strictEqual(Amount.from_quality('4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5C22A840E27DCA9B', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {inverse: true, base_currency: 'XRP'}).to_human_full(), '0.010251/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B');
});
it('BTC/USD human', function () {
assert.strictEqual(Amount.from_quality('6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC9858038D7EA4C68000', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {base_currency: 'BTC'}).to_human_full(), '1,000/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B');
});
it('BTC/USD inverse human', function () {
assert.strictEqual(Amount.from_quality('20294C923E80A51B487EB9547B3835FD483748B170D2D0A455071AFD498D0000', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {inverse: true, base_currency: 'BTC'}).to_human_full(), '0.5/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B');
});
it('XAU(dem)/XRP human', function () {
assert.strictEqual(Amount.from_quality('587322CCBDE0ABD01704769A73A077C32FB39057D813D4165F1FF973CAF997EF', 'XRP', NaN, {base_currency: '015841551A748AD2C1F76FF6ECB0CCCD00000000', reference_date: 443845330 + 31535000}).to_human_full(), '90,452.246928/XRP');
});
it('XAU(dem)/XRP inverse human', function () {
assert.strictEqual(Amount.from_quality('F72C7A9EAE4A45ED1FB547AD037D07B9B965C6E662BEBAFA4A03F2A976804235', 'XRP', NaN, {inverse: true, base_currency: '015841551A748AD2C1F76FF6ECB0CCCD00000000', reference_date: 443845330 + 31535000}).to_human_full(), '90,442.196677/XRP');
});
it('USD/XAU(dem) human', function () {
assert.strictEqual(Amount.from_quality('4743E58E44974B325D42FD2BB683A6E36950F350EE46DD3A521B644B99782F5F', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {base_currency: 'USD', reference_date: 443845330 + 31535000}).to_human_full(), '0.007710100231303007/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN');
});
it('USD/XAU(dem) inverse human', function () {
assert.strictEqual(Amount.from_quality('CDFD3AFB2F8C5DBEF75B081F7C957FF5509563266F28F36C5704A0FB0BAD8800', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {inverse: true, base_currency: 'USD', reference_date: 443845330 + 31535000}).to_human_full(), '0.007675186123263489/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN');
});
});
describe('apply interest', function() {
it ('from_json apply interest 10 XAU', function() {
var demAmount = Amount.from_json('10/0158415500000000C1F76FF6ECB0BAC600000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(demAmount.to_text_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
demAmount = demAmount.applyInterest(459990264);
assert.strictEqual(demAmount.to_text_full(), '9.294949401870435/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it ('from_json apply interest XAU', function() {
var demAmount = Amount.from_json('1235.5/0158415500000000C1F76FF6ECB0BAC600000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(demAmount.to_text_full(), '1235.5/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
demAmount = demAmount.applyInterest(459990264);
assert.strictEqual(demAmount.to_text_full(), '1148.390998601092/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it ('from_human with reference date', function() {
var demAmount = Amount.from_human('10 0158415500000000C1F76FF6ECB0BAC600000000', {reference_date:459990264});
demAmount.set_issuer("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh");
assert.strictEqual(demAmount.to_text_full(), '10.75853086191915/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it ('from_json apply interest 10 XAU human', function() {
var demAmount = Amount.from_json('10/0158415500000000C1F76FF6ECB0BAC600000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(demAmount.to_human_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
demAmount = demAmount.applyInterest(459990264);
assert.strictEqual(demAmount.to_human_full(), '9.294949401870435/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it ('from_json apply interest XAU human', function() {
var demAmount = Amount.from_json('1235.5/0158415500000000C1F76FF6ECB0BAC600000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(demAmount.to_human_full(), '1,235.5/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
demAmount = demAmount.applyInterest(459990264);
assert.strictEqual(demAmount.to_human_full(), '1,148.390998601092/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it ('from_human with reference date human', function() {
var demAmount = Amount.from_human('10 0158415500000000C1F76FF6ECB0BAC600000000', {reference_date:459990264});
demAmount.set_issuer("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh");
assert.strictEqual(demAmount.to_human_full(), '10.75853086191915/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
});
describe('amount limits', function() {
it ('max JSON wire limite', function() {
assert.strictEqual(Amount.bi_xns_max.toString(), '9000000000000000000');
});
it ('max JSON wire limite', function() {
assert.strictEqual(Amount.bi_xns_min.toString(), '-9000000000000000000');
});
it('max mantissa value', function() {
assert.strictEqual(Amount.bi_man_max_value.toString(), '9999999999999999');
});
it('min mantissa value', function() {
assert.strictEqual(Amount.bi_man_min_value.toString(), '1000000000000000');
});
it ('from_json minimum XRP', function() {
console.log('max', Amount.bi_xns_max.toString());
var amt = Amount.from_json('-9000000000000000000');
assert.strictEqual(amt.to_json(), '-9000000000000000000');
});
it ('from_json maximum XRP', function() {
var amt = Amount.from_json('-9000000000000000000');
assert.strictEqual(amt.to_json(), '-9000000000000000000');
});
it ('from_json less than minimum XRP', function() {
var amt = Amount.from_json('-9000000000000000001');
assert.strictEqual(amt.to_json(), '0');
});
it ('from_json more than maximum XRP', function() {
var amt = Amount.from_json('9000000000000000001');
assert.strictEqual(amt.to_json(), '0');
});
it ('from_json minimum IOU', function() {
var amt = Amount.from_json('-1e-81/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(amt._value.toString(), Amount.bi_man_min_value.toString());
assert.strictEqual(amt.to_text(), '-1000000000000000e-96');
assert.strictEqual(amt.to_text(), Amount.min_value);
});
it('from_json exceed minimum IOU', function() {
assert.throws(function() {
Amount.from_json('-1e-82/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')
}, 'Exceeding min value of ' + Amount.min_value);
});
it ('from_json maximum IOU', function() {
var amt = Amount.from_json('9999999999999999e80/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(amt._value.toString(), Amount.bi_man_max_value.toString());
assert.strictEqual(amt.to_text(), '9999999999999999e80');
});
it ('from_json exceed maximum IOU', function() {
assert.throws(function() {
Amount.from_json('9999999999999999e81/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')
}, 'Exceeding max value of ' + Amount.max_value);
});
it ('from_json normalize mantissa to valid max range, lost significant digits', function() {
var amt = Amount.from_json('99999999999999999999999999999999/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(amt._value.toString(), Amount.bi_man_max_value.toString());
assert.strictEqual(amt.to_text(), '9999999999999999e16');
});
it ('from_json normalize mantissa to min valid range, lost significant digits', function() {
var amt = Amount.from_json('-0.0000000000000000000000001/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(amt._value.toString(), Amount.bi_man_min_value.toString());
assert.strictEqual(amt.to_text(), '-1000000000000000e-40');
});
}); });
}); });

View File

@@ -1,6 +1,7 @@
var assert = require('assert'); var assert = require('assert');
var utils = require('./testutils'); var utils = require('./testutils');
var currency = utils.load_module('currency').Currency; var currency = utils.load_module('currency').Currency;
var timeUtil = utils.load_module('utils').time;
describe('Currency', function() { describe('Currency', function() {
describe('json_rewrite', function() { describe('json_rewrite', function() {
@@ -16,11 +17,22 @@ describe('Currency', function() {
}); });
}); });
describe('from_json', function() { 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() { it('from_json(NaN).to_json() == "XRP"', function() {
var r = currency.from_json(NaN); var r = currency.from_json(NaN);
assert(!r.is_valid()); assert(!r.is_valid());
assert.strictEqual('XRP', r.to_json()); 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() { it('from_json("XRP").to_json() == "XRP"', function() {
var r = currency.from_json('XRP'); var r = currency.from_json('XRP');
assert(r.is_valid()); assert(r.is_valid());
@@ -40,8 +52,8 @@ describe('Currency', function() {
}); });
it('from_json("1D2").to_human()', function() { it('from_json("1D2").to_human()', function() {
var r = currency.from_json("1D2"); var r = currency.from_json("1D2");
assert(!r.is_valid()); assert(r.is_valid());
assert.strictEqual('XRP', r.to_json()); assert.strictEqual('1D2', r.to_json());
}); });
it('from_json("XAU").to_json() hex', function() { it('from_json("XAU").to_json() hex', function() {
var r = currency.from_json("XAU"); var r = currency.from_json("XAU");
@@ -82,6 +94,9 @@ describe('Currency', function() {
var cur = currency.from_human('EUR (0.5361%pa)'); var cur = currency.from_human('EUR (0.5361%pa)');
assert.strictEqual(cur.to_json(), 'EUR (0.54%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: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); assert.strictEqual(cur.get_interest_percentage_at(undefined, 4), 0.5361);
}); });
it('From human "TYX - 30-Year Treasuries (1.5%pa)"', function() { it('From human "TYX - 30-Year Treasuries (1.5%pa)"', function() {
@@ -100,6 +115,16 @@ describe('Currency', function() {
var cur = currency.from_human('INR - 30 Indian Rupees'); var cur = currency.from_human('INR - 30 Indian Rupees');
assert.strictEqual(cur.to_json(), 'INR'); 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);
});
}); });
@@ -111,8 +136,28 @@ describe('Currency', function() {
assert.strictEqual('XRP', currency.from_json(NaN).to_human()); assert.strictEqual('XRP', currency.from_json(NaN).to_human());
}); });
it('"015841551A748AD2C1F76FF6ECB0CCCD00000000") == "015841551A748AD2C1F76FF6ECB0CCCD00000000"', function() { it('"015841551A748AD2C1F76FF6ECB0CCCD00000000") == "015841551A748AD2C1F76FF6ECB0CCCD00000000"', function() {
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human(), assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human(), 'XAU (-0.5%pa)');
'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() { 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'})); assert.strictEqual('USD - US Dollar', currency.from_json('USD').to_human({full_name:'US Dollar'}));
@@ -128,6 +173,7 @@ describe('Currency', function() {
var cur = currency.from_json("TIM"); var cur = currency.from_json("TIM");
assert.strictEqual(cur.to_human({full_name: null}), "TIM"); assert.strictEqual(cur.to_human({full_name: null}), "TIM");
}); });
}); });
describe('from_hex', function() { describe('from_hex', function() {
@@ -138,10 +184,30 @@ describe('Currency', function() {
assert.strictEqual(cur.to_json(), cur.to_human()); assert.strictEqual(cur.to_json(), cur.to_human());
}); });
}); });
describe('parse_json(currency obj)', function() { describe('parse_json', function() {
assert.strictEqual('USD', new currency().parse_json(currency.from_json('USD')).to_json()); 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());
assert.strictEqual('USD (0.5%pa)', new currency().parse_json(currency.from_json('USD (0.5%pa)')).to_json()); cur = currency.from_hex('015841551A748AD2C1F76FF6ECB0CCCD00000000');
cur2 = currency.from_json(cur);
assert.strictEqual(cur.to_json(), cur2.to_json());
});
it('should parse json 0', function() {
var cur = currency.from_json(0);
assert.strictEqual(cur.to_json(), 'XRP');
assert.strictEqual(cur.get_iso(), 'XRP');
});
it('should parse json 0', function() {
var cur = currency.from_json('0');
assert.strictEqual(cur.to_json(), 'XRP');
assert.strictEqual(cur.get_iso(), 'XRP');
});
}); });
describe('is_valid', function() { describe('is_valid', function() {
@@ -183,14 +249,16 @@ describe('Currency', function() {
return +(Math.round(num + "e+"+precision) + "e-"+precision); return +(Math.round(num + "e+"+precision) + "e-"+precision);
} }
describe('get_interest_at', function() { describe('get_interest_at', function() {
it('returns demurred value for demurrage currency', function() { it('should return demurred value for demurrage currency', function() {
var cur = currency.from_json('015841551A748AD2C1F76FF6ECB0CCCD00000000'); var cur = currency.from_json('015841551A748AD2C1F76FF6ECB0CCCD00000000');
// At start, no demurrage should occur // At start, no demurrage should occur
assert.equal(1, cur.get_interest_at(443845330)); 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 // 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(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 // After one demurrage period, 1/e should have occurred
assert.equal(1/Math.E, cur.get_interest_at(443845330 + 6291418827.05)); assert.equal(1/Math.E, cur.get_interest_at(443845330 + 6291418827.05));
@@ -201,6 +269,11 @@ describe('Currency', function() {
// One demurrage period before start, rate should be e // One demurrage period before start, rate should be e
assert.equal(Math.E, cur.get_interest_at(443845330 - 6291418827.05)); 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() { describe('get_iso', function() {
it('should get "XRP" iso_code', function() { it('should get "XRP" iso_code', function() {

View File

@@ -82,8 +82,17 @@ describe('Message', function(){
}); });
it('should throw an error if given an invalid secret key', function(){ 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.
var secret_string = 'badsafRpB5euNL52PZPTSqrE9gvuFwTC'; // 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'; var hash = 'e865bcc63a86ef21585ac8340a7cc8590ed85175a2a718c6fb2bfb2715d13778';
assert.throws(function(){ assert.throws(function(){
@@ -261,7 +270,8 @@ describe('Message', function(){
//Remote.prototype.addServer = function(){}; //Remote.prototype.addServer = function(){};
var test_remote = new Remote(); var test_remote = new Remote();
test_remote.state = 'online'; test_remote.state = 'online';
test_remote.request_account_info = function(account, callback) { test_remote.requestAccountInfo = function(options, callback) {
var account = options.account;
if (account === data.account) { if (account === data.account) {
callback(null, { callback(null, {
"account_data": { "account_data": {
@@ -297,7 +307,8 @@ describe('Message', function(){
//Remote.prototype.addServer = function(){}; //Remote.prototype.addServer = function(){};
var test_remote = new Remote(); var test_remote = new Remote();
test_remote.state = 'online'; test_remote.state = 'online';
test_remote.request_account_info = function(account, callback) { test_remote.requestAccountInfo = function(options, callback) {
var account = options.account;
if (account === data.account) { if (account === data.account) {
callback(null, { callback(null, {
"account_data": { "account_data": {

1547
test/orderbook-test.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,14 @@ var Remote = utils.load_module('remote').Remote;
var Server = utils.load_module('server').Server; var Server = utils.load_module('server').Server;
var Request = utils.load_module('request').Request; var Request = utils.load_module('request').Request;
var options, spy, mock, stub, remote, callback, database, tx; 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 () { describe('Remote', function () {
beforeEach(function () { beforeEach(function () {
@@ -28,19 +35,19 @@ describe('Remote', function () {
// 'bitcoin': 'localhost:3000' // 'bitcoin': 'localhost:3000'
// 'bitcoin': 'https://www.bitstamp.net/ripple/bridge/out/bitcoin/' // 'bitcoin': 'https://www.bitstamp.net/ripple/bridge/out/bitcoin/'
} }
}, }
}; };
}) });
it('remote server initialization - url object', function() { it('remote server initialization - url object', function() {
var remote = new Remote({ var remote = new Remote({
servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ], servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ]
}); });
assert(Array.isArray(remote._servers)); assert(Array.isArray(remote._servers));
assert(remote._servers[0] instanceof Server); assert(remote._servers[0] instanceof Server);
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
}) });
it('remote server initialization - url object - no secure property', function() { it('remote server initialization - url object - no secure property', function() {
var remote = new Remote({ var remote = new Remote({
@@ -49,7 +56,7 @@ describe('Remote', function () {
assert(Array.isArray(remote._servers)); assert(Array.isArray(remote._servers));
assert(remote._servers[0] instanceof Server); assert(remote._servers[0] instanceof Server);
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
}) });
it('remote server initialization - url object - secure: false', function() { it('remote server initialization - url object - secure: false', function() {
var remote = new Remote({ var remote = new Remote({
@@ -67,7 +74,7 @@ describe('Remote', function () {
assert(Array.isArray(remote._servers)); assert(Array.isArray(remote._servers));
assert(remote._servers[0] instanceof Server); assert(remote._servers[0] instanceof Server);
assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443');
}) });
it('remote server initialization - url object - invalid host', function() { it('remote server initialization - url object - invalid host', function() {
assert.throws( assert.throws(
@@ -76,7 +83,7 @@ describe('Remote', function () {
servers: [ { host: '+', port: 443, secure: true } ] servers: [ { host: '+', port: 443, secure: true } ]
}); });
}, Error); }, Error);
}) });
it('remote server initialization - url object - invalid port', function() { it('remote server initialization - url object - invalid port', function() {
assert.throws( assert.throws(
@@ -144,7 +151,35 @@ describe('Remote', function () {
); );
}); });
it('request constructors', function () { it('remote server initialization - set max_fee - number', function() {
var remote = new Remote({
max_fee: 10
});
assert.strictEqual(remote.max_fee, 10);
remote = new Remote({
max_fee: 1234567890
});
assert.strictEqual(remote.max_fee, 1234567890);
});
it('remote server initialization - set max_fee - string fails, should be number', function() {
var remote = new Remote({
max_fee: '1234567890'
});
assert.strictEqual(remote.max_fee, 1e6);
});
it('remote server initialization - max_fee - default', function() {
var remote = new Remote({
max_fee: void(0)
});
assert.strictEqual(remote.max_fee, 1e6);
assert.strictEqual(remote.max_fee, 1000000);
assert.strictEqual((new Remote()).max_fee, 1e6);
});
describe('request constructors', function () {
beforeEach(function () { beforeEach(function () {
callback = function () {} callback = function () {}
remote = new Remote(options); remote = new Remote(options);
@@ -184,7 +219,222 @@ describe('Remote', function () {
var request = remote.request_unl_delete(null, {}, callback); var request = remote.request_unl_delete(null, {}, callback);
assert(request instanceof Request); 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() { it('create remote and get pending transactions', function() {
before(function() { before(function() {
@@ -244,7 +494,7 @@ describe('Remote', function () {
callback(null, tx); callback(null, tx);
} }
} }
}) });
it('should set transaction members correct ', function(done) { it('should set transaction members correct ', function(done) {
remote = new Remote(options); remote = new Remote(options);
@@ -267,9 +517,9 @@ describe('Remote', function () {
}, },
parseJson: function(json) {} parseJson: function(json) {}
} }
} };
remote.getPendingTransactions(); remote.getPendingTransactions();
}) })
}) })
}) });

View File

@@ -326,6 +326,7 @@ describe('Request', function() {
var request = new Request(remote, 'server_info'); var request = new Request(remote, 'server_info');
request.ledgerChoose(); request.ledgerChoose();
assert.strictEqual(request.message.ledger_hash, 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); assert.strictEqual(request.message.ledger_hash, 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE');
assert.strictEqual(request.message.ledger_index, void(0));
}); });
it('Select ledger - identifier', function() { it('Select ledger - identifier', function() {
@@ -335,6 +336,7 @@ describe('Request', function() {
var request = new Request(remote, 'server_info'); var request = new Request(remote, 'server_info');
request.ledgerSelect('validated'); request.ledgerSelect('validated');
assert.strictEqual(request.message.ledger_index, 'validated'); assert.strictEqual(request.message.ledger_index, 'validated');
assert.strictEqual(request.message.ledger_hash, void(0));
}); });
it('Select ledger - index', function() { it('Select ledger - index', function() {
@@ -344,6 +346,7 @@ describe('Request', function() {
var request = new Request(remote, 'server_info'); var request = new Request(remote, 'server_info');
request.ledgerSelect(7016915); request.ledgerSelect(7016915);
assert.strictEqual(request.message.ledger_index, 7016915); assert.strictEqual(request.message.ledger_index, 7016915);
assert.strictEqual(request.message.ledger_hash, void(0));
}); });
it('Select ledger - hash', function() { it('Select ledger - hash', function() {
@@ -353,15 +356,23 @@ describe('Request', function() {
var request = new Request(remote, 'server_info'); var request = new Request(remote, 'server_info');
request.ledgerSelect('B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); request.ledgerSelect('B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE');
assert.strictEqual(request.message.ledger_hash, 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); assert.strictEqual(request.message.ledger_hash, 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE');
assert.strictEqual(request.message.ledger_index, void(0));
}); });
it('Select ledger - hash', function() { it('Select ledger - undefined', function() {
var remote = new Remote(); var remote = new Remote();
remote._connected = true; remote._connected = true;
var request = new Request(remote, 'server_info'); var request = new Request(remote, 'server_info');
request.ledgerSelect('B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); request.ledgerSelect();
assert.strictEqual(request.message.ledger_hash, 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); 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() { it('Set account_root', function() {

View File

@@ -5,59 +5,71 @@ var config = require('./testutils').get_config();
describe('Seed', function() { describe('Seed', function() {
it('can generate many addresses', function () { it('can generate many addresses', function () {
var seed = Seed.from_json("masterpassphrase");
// Note: Created with jRippleAPI code var test_data = [
// Link: https://github.com/pmarches/jRippleAPI/blob/master/src/jrippleapi/keys/RippleDeterministicKeyGenerator.java // 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"]
];
// To reviewer/merger: Even generating keypairs for thi this small amount of function assert_helper(seed_json, address_or_nth, expected) {
// addresses makes the test suite run SO SLOW. Consider cutting at the tail. var seed = Seed.from_json(seed_json);
var first_nth_addresses = [ var keypair = seed.get_key(address_or_nth, 500);
// 0th implied assert.strictEqual(keypair.to_hex_pub(), expected);
"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
// 1th implied
"r4bYF7SLUMD7QgSLLpgJx38WJSY12ViRjP",
// 2th implied
"rLpAd4peHUMBPbVJASMYK5GTBUSwXRD9nx",
// ...
"rN9HWHPAftXC6xNxJRqgVr1HexpUi6FBUa",
"rhwPfeEkeQh2vqoKGPBpPyS9nKmKZdpypu",
"rKdNfPfrzfisDCPCqK67YhDLezuVKGKXNm",
"rsn4NeGzMRvMn5ABQxmt9VNEkSknVneBK4",
"rUSJGFH1kQnaKpoAhkArfyw6HZVe8GT5w3",
"r3EYp6isx2Row5pu19C4Ujvp68oEEabhuA",
"rGiYpnAn9DTXiM78CCJcYAuL6QHEDdazHA",
"rVrRVeqqEJHAove5B9TqBAHEXBTzMi5eu",
"rJbarThDXYXxtCgDxRoTCDf2NoYdgSjuVk",
"rfuWNJ1TkQzoZZcc4K8wmtsUiGwURFbed1",
"rpuTqebfbZZdKEUV3bjecoViNL4W4gepWZ",
"r42ywHUco4C2AjgaSmYM7uX13Kbz6GdDKp",
"rnWb45MLd5hQiB6Arr94DdY2z95quL7XNf",
"rMukaQ87bfAeddcT16RtgS3RbFmaQWrSGu",
"rN6dMHU51K9y8eVMhjQMsQVp5gPwt3eYYx",
"rfRFyeJDNqpfZVFmjNj9tNzFVsCNAWKtNc",
"rMoGSX48KrT31HKx9KfMSnYhjvRw3YYzQW",
"rfTiEfbeVsYU7QEe1Zfogiz7h43x6ChKGC"
]
function assert_helper(account_id, expected) {
var keypair = seed.get_key(account_id);
assert.strictEqual(keypair.get_address().to_json(),
expected);
} }
for (var nth = 0; nth < first_nth_addresses.length; nth++) {
var expected = first_nth_addresses[nth], looking_for; 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 //`seed.get_key($ripple_address)` is arguably an ill concieved feature
// as it needs to generate `nth` many keypairs and generate hashed public // as it needs to generate `nth` many keypairs and generate hashed public
// keys (addresses) for equality tests ?? // keys (addresses) for equality tests ??
// Would need remote.set_secret(address, private_key_not_seed) ?? // Would need remote.set_secret(address, private_key_not_seed) ??
assert_helper((looking_for = expected), expected) assert_helper(seed_json, address, expected);
// This isn't too bad as it only needs to generate one keypair `seq` // This isn't too bad as it only needs to generate one keypair `seq`
assert_helper((looking_for = nth), expected) 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 // vim:sw=2:sts=2:ts=8:et

View File

@@ -1,8 +1,23 @@
var utils = require('./testutils'); var utils = require('./testutils');
var assert = require('assert'); var assert = require('assert');
var SerializedObject = utils.load_module('serializedobject').SerializedObject; var SerializedObject = utils.load_module('serializedobject').SerializedObject;
var sjcl = require('./../src/js/ripple/utils').sjcl;
// Shortcuts
var hex = sjcl.codec.hex;
var bytes = sjcl.codec.bytes;
var utf8 = sjcl.codec.utf8String;
describe('Serialized object', function() { describe('Serialized object', function() {
function convertStringToHex(string) {
return hex.fromBits(utf8.toBits(string)).toUpperCase();
}
function convertHexToString(hexString) {
return utf8.fromBits(hex.toBits(hexString));
}
describe('#from_json(v).to_json() == v', function(){ describe('#from_json(v).to_json() == v', function(){
it('outputs same as passed to from_json', function() { it('outputs same as passed to from_json', function() {
var input_json = { var input_json = {
@@ -35,6 +50,7 @@ describe('Serialized object', function() {
assert.deepEqual(input_json, output_json); assert.deepEqual(input_json, output_json);
}); });
}); });
describe('#from_json', function() { describe('#from_json', function() {
it('understands TransactionType as a Number', function() { it('understands TransactionType as a Number', function() {
var input_json = { var input_json = {
@@ -52,6 +68,7 @@ describe('Serialized object', function() {
assert.equal(0, input_json.TransactionType); assert.equal(0, input_json.TransactionType);
assert.equal("Payment", output_json.TransactionType); assert.equal("Payment", output_json.TransactionType);
}); });
it('understands LedgerEntryType as a Number', function() { it('understands LedgerEntryType as a Number', function() {
var input_json = { var input_json = {
// no, non required fields // no, non required fields
@@ -65,6 +82,7 @@ describe('Serialized object', function() {
assert.equal(100, input_json.LedgerEntryType); assert.equal(100, input_json.LedgerEntryType);
assert.equal("DirectoryNode", output_json.LedgerEntryType); assert.equal("DirectoryNode", output_json.LedgerEntryType);
}); });
describe('Format validation', function() { describe('Format validation', function() {
// Peercover actually had a problem submitting transactions without a `Fee` // Peercover actually had a problem submitting transactions without a `Fee`
// and rippled was only informing of "transaction is invalid" // and rippled was only informing of "transaction is invalid"
@@ -80,15 +98,231 @@ describe('Serialized object', function() {
}; };
assert.throws ( assert.throws (
function() { function() {
var output_json = SerializedObject.from_json(input_json); SerializedObject.from_json(input_json);
}, },
/Payment is missing fields: \["Fee"\]/ /Payment is missing fields: \["Fee"\]/
); );
}); });
}); });
}) describe('Memos', function() {
var input_json;
beforeEach(function() {
input_json = {
"Flags": 2147483648,
"TransactionType": "Payment",
"Account": "rhXzSyt1q9J8uiFXpK3qSugAAPJKXLtnrF",
"Amount": "1",
"Destination": "radqi6ppXFxVhJdjzaATRBxdrPcVTf1Ung",
"Sequence": 281,
"SigningPubKey": "03D642E6457B8AB4D140E2C66EB4C484FAFB1BF267CB578EC4815FE6CD06379C51",
"Fee": "12000",
"LastLedgerSequence": 10074214,
"TxnSignature": "304402201180636F2CE215CE97A29CD302618FAE60D63EBFC8903DE17A356E857A449C430220290F4A54F9DE4AC79034C8BEA5F1F8757F7505F1A6FF04D2E19B6D62E867256B"
};
});
it('should serialize and parse - full memo, all strings text/plain ', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoFormat": "text",
"MemoData": "some data"
}
}
];
var so = SerializedObject.from_json(input_json).to_json();
input_json.Memos[0].Memo.parsed_memo_type = 'test';
input_json.Memos[0].Memo.parsed_memo_format = 'text';
input_json.Memos[0].Memo.parsed_memo_data = 'some data';
input_json.Memos[0].Memo.MemoType = convertStringToHex('test');
input_json.Memos[0].Memo.MemoFormat = convertStringToHex('text');
input_json.Memos[0].Memo.MemoData = convertStringToHex('some data');
assert.deepEqual(so, input_json);
});
it('should serialize and parse - full memo, all strings, invalid MemoFormat', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoFormat": "application/json",
"MemoData": "some data"
}
}
];
var so = SerializedObject.from_json(input_json).to_json();
input_json.Memos[0].Memo.parsed_memo_type = 'test';
input_json.Memos[0].Memo.parsed_memo_format = 'application/json';
input_json.Memos[0].Memo.MemoType = convertStringToHex('test');
input_json.Memos[0].Memo.MemoFormat = convertStringToHex('application/json');
input_json.Memos[0].Memo.MemoData = convertStringToHex('some data');
assert.deepEqual(so, input_json);
assert.strictEqual(input_json.Memos[0].Memo.parsed_memo_data, void(0));
});
it('should throw an error - full memo, json data, invalid MemoFormat', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoFormat": "text",
"MemoData": {
"string" : "some_string",
"boolean" : true
}
}
}
];
assert.throws(function() {
SerializedObject.from_json(input_json);
}, /^Error: MemoData can only be a JSON object with a valid json MemoFormat \(Memo\) \(Memos\)/);
});
it('should serialize and parse - full memo, json data, valid MemoFormat, ignored field', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoFormat": "json",
"ignored" : "ignored",
"MemoData": {
"string" : "some_string",
"boolean" : true
}
}
}
];
var so = SerializedObject.from_json(input_json).to_json();
delete input_json.Memos[0].Memo.ignored;
input_json.Memos[0].Memo.parsed_memo_type = 'test';
input_json.Memos[0].Memo.parsed_memo_format = 'json';
input_json.Memos[0].Memo.parsed_memo_data = {
"string" : "some_string",
"boolean" : true
};
input_json.Memos[0].Memo.MemoType = convertStringToHex('test');
input_json.Memos[0].Memo.MemoFormat = convertStringToHex('json');
input_json.Memos[0].Memo.MemoData = convertStringToHex(JSON.stringify(
{
"string" : "some_string",
"boolean" : true
}
));
assert.deepEqual(so, input_json);
});
it('should serialize and parse - full memo, json data, valid MemoFormat', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoFormat": "json",
"MemoData": {
"string" : "some_string",
"boolean" : true
}
}
}
];
var so = SerializedObject.from_json(input_json).to_json();
input_json.Memos[0].Memo.parsed_memo_type = 'test';
input_json.Memos[0].Memo.parsed_memo_format = 'json';
input_json.Memos[0].Memo.parsed_memo_data = {
"string" : "some_string",
"boolean" : true
};
input_json.Memos[0].Memo.MemoType = convertStringToHex('test');
input_json.Memos[0].Memo.MemoFormat = convertStringToHex('json');
input_json.Memos[0].Memo.MemoData = convertStringToHex(JSON.stringify(
{
"string" : "some_string",
"boolean" : true
}
));
assert.deepEqual(so, input_json);
});
it('should serialize and parse - full memo, json data, valid MemoFormat, integer', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoFormat": "json",
"MemoData": 3
}
}
];
var so = SerializedObject.from_json(input_json).to_json();
input_json.Memos[0].Memo.parsed_memo_type = 'test';
input_json.Memos[0].Memo.parsed_memo_format = 'json';
input_json.Memos[0].Memo.parsed_memo_data = 3;
input_json.Memos[0].Memo.MemoType = convertStringToHex('test');
input_json.Memos[0].Memo.MemoFormat = convertStringToHex('json');
input_json.Memos[0].Memo.MemoData = convertStringToHex(JSON.parse(3));
assert.deepEqual(so, input_json);
});
it('should throw an error - invalid Memo field', function() {
input_json.Memos = [
{
"Memo": {
"MemoType": "test",
"MemoParty": "json",
"MemoData": 3
}
}
];
assert.throws(function() {
SerializedObject.from_json(input_json);
}, /^Error: JSON contains unknown field: "MemoParty" \(Memo\) \(Memos\)/);
});
it('should serialize json with memo - match hex output', function() {
var input_json = {
Flags: 2147483648,
TransactionType: 'Payment',
Account: 'rhXzSyt1q9J8uiFXpK3qSugAAPJKXLtnrF',
Amount: '1',
Destination: 'radqi6ppXFxVhJdjzaATRBxdrPcVTf1Ung',
Memos: [
{
Memo: {
MemoType: 'image'
}
}
],
Sequence: 294,
SigningPubKey: '03D642E6457B8AB4D140E2C66EB4C484FAFB1BF267CB578EC4815FE6CD06379C51',
Fee: '12000',
LastLedgerSequence: 10404607,
TxnSignature: '304402206B53EDFA6EFCF6FE5BA76C81BABB60A3B55E9DE8A1462DEDC5F387879575E498022015AE7B59AA49E735D7F2E252802C4406CD00689BCE5057C477FE979D38D2DAC9'
};
var serializedHex = '12000022800000002400000126201B009EC2FF614000000000000001684000000000002EE0732103D642E6457B8AB4D140E2C66EB4C484FAFB1BF267CB578EC4815FE6CD06379C517446304402206B53EDFA6EFCF6FE5BA76C81BABB60A3B55E9DE8A1462DEDC5F387879575E498022015AE7B59AA49E735D7F2E252802C4406CD00689BCE5057C477FE979D38D2DAC9811426C4CFB3BD05A9AA23936F2E81634C66A9820C9483143DD06317D19C6110CAFF150AE528F58843BE2CA1F9EA7C05696D616765E1F1';
assert.strictEqual(SerializedObject.from_json(input_json).to_hex(), serializedHex);
});
});
});
}); });
// vim:sw=2:sts=2:ts=8:et
// vim:sw=2:sts=2:ts=8:et

View File

@@ -2,6 +2,7 @@ var utils = require('./testutils');
var assert = require('assert'); var assert = require('assert');
var SerializedObject = utils.load_module('serializedobject').SerializedObject; var SerializedObject = utils.load_module('serializedobject').SerializedObject;
var types = utils.load_module('serializedtypes'); var types = utils.load_module('serializedtypes');
var amountConstants = require('../src/js/ripple/amount').consts;
var BigInteger = require('../src/js/jsbn/jsbn').BigInteger; var BigInteger = require('../src/js/jsbn/jsbn').BigInteger;
var config = require('./testutils').get_config(); var config = require('./testutils').get_config();
@@ -550,6 +551,11 @@ describe('Serialized types', function() {
}); });
assert.strictEqual(so.to_hex(), 'D5438D7EA4C68000015841551A748AD23FEFFFFFFFEA028000000000E4FE687C90257D3D2D694C8531CDEECBE84F3367'); assert.strictEqual(so.to_hex(), 'D5438D7EA4C68000015841551A748AD23FEFFFFFFFEA028000000000E4FE687C90257D3D2D694C8531CDEECBE84F3367');
}); });
it('Serialize max_value/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () {
var so = new SerializedObject();
types.Amount.serialize(so, amountConstants.max_value+'/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(so.to_hex(), 'EC6386F26FC0FFFF0000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8');
});
it('Parse 1 XRP', function () { it('Parse 1 XRP', function () {
var so = new SerializedObject('4000000000000001'); var so = new SerializedObject('4000000000000001');
assert.strictEqual(types.Amount.parse(so).to_json(), '1'); assert.strictEqual(types.Amount.parse(so).to_json(), '1');
@@ -582,6 +588,10 @@ describe('Serialized types', function() {
var so = new SerializedObject('94838D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8'); var so = new SerializedObject('94838D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8');
assert.strictEqual(types.Amount.parse(so).to_text_full(), '-1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); assert.strictEqual(types.Amount.parse(so).to_text_full(), '-1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
}); });
it('Parse max_value/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () {
var so = new SerializedObject('EC6386F26FC0FFFF0000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8');
assert.strictEqual(types.Amount.parse(so).to_text_full(), amountConstants.max_value+'/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
}); });
describe('Account', function() { describe('Account', function() {

View File

@@ -478,6 +478,103 @@ describe('Server', function() {
Server.websocketConstructor = websocketConstructor; Server.websocketConstructor = websocketConstructor;
}); });
it('Connect - partial history disabled', function(done) {
var wss = new ws.Server({ port: 5748 });
wss.once('connection', function(ws) {
ws.once('message', function(message) {
var m = JSON.parse(message);
assert.deepEqual(m, {
command: 'subscribe',
id: 0,
streams: [ 'ledger', 'server' ]
});
ws.send(JSON.stringify({
id: 0,
status: 'success',
type: 'response',
result: {
fee_base: 10,
fee_ref: 10,
ledger_hash: '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776',
ledger_index: 7053695,
ledger_time: 455414390,
load_base: 256,
load_factor: 256,
random: 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A',
reserve_base: 20000000,
reserve_inc: 5000000,
server_status: 'syncing',
validated_ledgers: '3175520-3176615'
}
}));
wss.close();
});
});
var server = new Server(new Remote({ allow_partial_history: false }), 'ws://localhost:5748');
server.reconnect = function() {
setImmediate(function() {
done();
});
};
server.once('connect', function() {
assert(false, 'Should not connect');
});
server.connect();
});
it('Connect - syncing state', function(done) {
// Test that fee and load defaults are not overwritten by
// undefined properties on server subscribe response
var wss = new ws.Server({ port: 5748 });
wss.once('connection', function(ws) {
ws.once('message', function(message) {
var m = JSON.parse(message);
assert.deepEqual(m, {
command: 'subscribe',
id: 0,
streams: [ 'ledger', 'server' ]
});
ws.send(JSON.stringify({
id: 0,
status: 'success',
type: 'response',
result: {
load_base: 256,
load_factor: 256,
server_status: 'syncing'
}
}));
wss.close();
});
});
var server = new Server(new Remote(), 'ws://localhost:5748');
server.once('connect', function() {
assert(server.isConnected());
assert.strictEqual(server._load_base, 256);
assert.strictEqual(server._load_factor, 256);
assert.strictEqual(server._fee_base, 10);
assert.strictEqual(server._fee_ref, 10);
done();
});
server.connect();
});
it('Reconnect', function(done) { it('Reconnect', function(done) {
var server = new Server(new Remote(), 'ws://localhost:5748'); var server = new Server(new Remote(), 'ws://localhost:5748');
server._connected = true; server._connected = true;
@@ -572,10 +669,13 @@ describe('Server', function() {
var server = new Server(new Remote(), 'ws://localhost:5748'); var server = new Server(new Remote(), 'ws://localhost:5748');
server.once('disconnect', function() {
done();
});
server.once('connect', function() { server.once('connect', function() {
server._retryConnect = function(){ server._retryConnect = function(){
wss.close(); wss.close();
done();
}; };
server._ws.emit('error', new Error()); server._ws.emit('error', new Error());
}); });
@@ -906,12 +1006,6 @@ describe('Server', function() {
assert(server._isConnected()); assert(server._isConnected());
}); });
it('Compute fee - transaction', function() {
var server = new Server(new Remote(), 'ws://localhost:5748');
var transaction = new Transaction();
assert.strictEqual(server._computeFee(transaction), '12');
});
it('Compute fee - fee units', function() { it('Compute fee - fee units', function() {
var server = new Server(new Remote(), 'ws://localhost:5748'); var server = new Server(new Remote(), 'ws://localhost:5748');
var transaction = new Transaction(); var transaction = new Transaction();
@@ -933,7 +1027,7 @@ describe('Server', function() {
server._load_factor = 256 * 4; server._load_factor = 256 * 4;
var transaction = new Transaction(); var transaction = new Transaction();
assert.strictEqual(server._computeFee(transaction), '48'); assert.strictEqual(server._computeFee(10), '48');
}); });
it('Compute reserve', function() { it('Compute reserve', function() {
@@ -1025,15 +1119,13 @@ describe('Server', function() {
server.once('connect', function() { server.once('connect', function() {
var receivedSubscribe = false; var receivedSubscribe = false;
assert.strictEqual(server._hostid, '');
server.once('response_server_info', function() { server.once('response_server_info', function() {
receivedSubscribe = true; receivedSubscribe = true;
}); });
server.once('disconnect', function() { server.once('disconnect', function() {
assert(receivedSubscribe); assert(receivedSubscribe);
assert.strictEqual(server.getHostID(), '(n94pSqypSfddzAVj9qoezHyUoetsrMnwgNuBqRJ3WHvM8aMMf7rW)'); assert.strictEqual(server.getServerID(), 'ws://localhost:5748 (n94pSqypSfddzAVj9qoezHyUoetsrMnwgNuBqRJ3WHvM8aMMf7rW)');
done(); done();
}); });
}); });

View File

@@ -26,8 +26,9 @@ describe('Signing', function() {
assert(_isNaN(new Seed().parse_json('').to_json())); assert(_isNaN(new Seed().parse_json('').to_json()));
}); });
it('hex string', function() { it('hex string', function() {
// 32 0s is a valid hex repr of seed bytes
var str = new Array(33).join('0'); var str = new Array(33).join('0');
assert(_isNaN(new Seed().parse_json(str).to_json())); assert.strictEqual((new Seed().parse_json(str).to_json()), 'sp6JS7f14BuwFY8Mw6bTtLKWauoUs');
}); });
it('passphrase', function() { it('passphrase', function() {
var str = new Array(60).join('0'); var str = new Array(60).join('0');

View File

@@ -30,7 +30,7 @@ describe('ECDSA signing with recoverable public key', function(){
// signature: 'AAAAG8L/yOA3nNqK4aOiQWJmOaWvkvr3NoTk6wCdX97U3qowdgFd98UK3evWV16qO3RHgFMEnUW/Vt4+kcidqW6hMo0=' // signature: 'AAAAG8L/yOA3nNqK4aOiQWJmOaWvkvr3NoTk6wCdX97U3qowdgFd98UK3evWV16qO3RHgFMEnUW/Vt4+kcidqW6hMo0='
}]; }];
var curve = sjcl.ecc.curves['c256']; var curve = sjcl.ecc.curves['k256'];
for (var m = 0; m < messages.length; m++) { for (var m = 0; m < messages.length; m++) {
@@ -64,7 +64,7 @@ describe('ECDSA signing with recoverable public key', function(){
// // TODO: figure out why bitcoinjs-lib and this produce different signature values // // TODO: figure out why bitcoinjs-lib and this produce different signature values
// var curve = sjcl.ecc.curves['c256']; // var curve = sjcl.ecc.curves['k256'];
// var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; // var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
// var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); // var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
@@ -85,7 +85,7 @@ describe('ECDSA signing with recoverable public key', function(){
it('should produce an error if the hash is not given as a bitArray', function(){ it('should produce an error if the hash is not given as a bitArray', function(){
var curve = sjcl.ecc.curves['c256']; var curve = sjcl.ecc.curves['k256'];
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
@@ -100,7 +100,7 @@ describe('ECDSA signing with recoverable public key', function(){
it('should return a bitArray', function(){ it('should return a bitArray', function(){
var curve = sjcl.ecc.curves['c256']; var curve = sjcl.ecc.curves['k256'];
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
@@ -114,7 +114,7 @@ describe('ECDSA signing with recoverable public key', function(){
it('should return a bitArray where the first word contains the recovery factor', function(){ it('should return a bitArray where the first word contains the recovery factor', function(){
var curve = sjcl.ecc.curves['c256']; var curve = sjcl.ecc.curves['k256'];
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
@@ -145,7 +145,7 @@ describe('ECDSA signing with recoverable public key', function(){
it('should produce an error if the signature given does not have the recovery factor prefix', function(){ it('should produce an error if the signature given does not have the recovery factor prefix', function(){
var curve = sjcl.ecc.curves['c256']; var curve = sjcl.ecc.curves['k256'];
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
@@ -162,7 +162,7 @@ describe('ECDSA signing with recoverable public key', function(){
it('should produce an error if it is not given both the hash and the signature', function(){ it('should produce an error if it is not given both the hash and the signature', function(){
var curve = sjcl.ecc.curves['c256']; var curve = sjcl.ecc.curves['k256'];
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
@@ -183,7 +183,7 @@ describe('ECDSA signing with recoverable public key', function(){
it('should produce an error if it cannot generate a valid public key from the the signature', function(){ 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 curve = sjcl.ecc.curves['k256'];
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
@@ -203,7 +203,7 @@ describe('ECDSA signing with recoverable public key', function(){
it('should return a publicKey object', function(){ it('should return a publicKey object', function(){
var curve = sjcl.ecc.curves['c256']; var curve = sjcl.ecc.curves['k256'];
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
@@ -219,7 +219,7 @@ describe('ECDSA signing with recoverable public key', function(){
it('tampering with the signature should produce a different public key, if it produces a valid one at all', function(){ 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 curve = sjcl.ecc.curves['k256'];
var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d'; var secret_hex = '9e623166ac44d4e75fa842f3443485b9c8380551132a8ffaa898b5c93bb18b7d';
var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex)); var secret_bn = sjcl.bn.fromBits(sjcl.codec.hex.toBits(secret_hex));
var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn); var secret_key = new sjcl.ecc.ecdsa.secretKey(curve, secret_bn);
@@ -238,6 +238,22 @@ describe('ECDSA signing with recoverable public key', function(){
}); });
it('should return a publicKey object, while using the overridden c256 curve', 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);
});
}); });
}); });

View File

@@ -1,9 +1,10 @@
var utils = require('./testutils'); var utils = require('./testutils');
var assert = require('assert'); var assert = require('assert');
var Amount = utils.load_module('amount').Amount; var Amount = utils.load_module('amount').Amount;
var Transaction = utils.load_module('transaction').Transaction; var Transaction = utils.load_module('transaction').Transaction;
var Remote = utils.load_module('remote').Remote; var TransactionQueue = utils.load_module('transactionqueue').TransactionQueue;
var Server = utils.load_module('server').Server; var Remote = utils.load_module('remote').Remote;
var Server = utils.load_module('server').Server;
var transactionResult = { var transactionResult = {
engine_result: 'tesSUCCESS', engine_result: 'tesSUCCESS',
@@ -38,7 +39,7 @@ describe('Transaction', function() {
it('Success listener', function(done) { it('Success listener', function(done) {
var transaction = new Transaction(); var transaction = new Transaction();
transaction.once('cleanup', function(message) { transaction.once('final', function(message) {
assert.deepEqual(message, transactionResult); assert.deepEqual(message, transactionResult);
assert(transaction.finalized); assert(transaction.finalized);
assert.strictEqual(transaction.state, 'validated'); assert.strictEqual(transaction.state, 'validated');
@@ -51,7 +52,7 @@ describe('Transaction', function() {
it('Error listener', function(done) { it('Error listener', function(done) {
var transaction = new Transaction(); var transaction = new Transaction();
transaction.once('cleanup', function(message) { transaction.once('final', function(message) {
assert.deepEqual(message, transactionResult); assert.deepEqual(message, transactionResult);
assert(transaction.finalized); assert(transaction.finalized);
assert.strictEqual(transaction.state, 'failed'); assert.strictEqual(transaction.state, 'failed');
@@ -130,25 +131,29 @@ describe('Transaction', function() {
it('Set state', function(done) { it('Set state', function(done) {
var transaction = new Transaction(); var transaction = new Transaction();
transaction.state = 'pending';
assert.strictEqual(transaction.state, 'unsubmitted');
var receivedEvents = 0; var receivedEvents = 0;
var events = 2; var events = [
'submitted',
'pending',
'validated'
];
transaction.once('state', function(state) { transaction.on('state', function(state) {
assert.strictEqual(state, 'validated'); receivedEvents++;
if (++receivedEvents === events) { assert(events.indexOf(state) > -1, 'Invalid state: ' + state);
done();
}
}); });
transaction.once('save', function() { transaction.setState(events[0]);
if (++receivedEvents === events) { transaction.setState(events[1]);
done(); transaction.setState(events[1]);
} transaction.setState(events[2]);
});
transaction.setState('validated'); assert.strictEqual(receivedEvents, 3);
assert.strictEqual(transaction.state, events[2]);
done();
}); });
it('Finalize submission', function() { it('Finalize submission', function() {
@@ -229,6 +234,11 @@ describe('Transaction', function() {
assert.strictEqual(transaction._computeFee(), '72'); assert.strictEqual(transaction._computeFee(), '72');
}); });
it('Compute fee, no remote', function() {
var transaction = new Transaction();
assert.strictEqual(transaction._computeFee(10), void(0));
});
it('Compute fee - no connected server', function() { it('Compute fee - no connected server', function() {
var remote = new Remote(); var remote = new Remote();
@@ -292,9 +302,7 @@ describe('Transaction', function() {
var s3 = new Server(remote, 'wss://s-west.ripple.com:443'); var s3 = new Server(remote, 'wss://s-west.ripple.com:443');
s3._connected = true; s3._connected = true;
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
s3._load_factor = (256 * 7) + 1; s3._load_factor = (256 * 7) + 1;
// Is this ever possible? <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
var s4 = new Server(remote, 'wss://s-west.ripple.com:443'); var s4 = new Server(remote, 'wss://s-west.ripple.com:443');
s4._connected = true; s4._connected = true;
@@ -373,6 +381,16 @@ describe('Transaction', function() {
done(); done();
}); });
it('Complete transaction, local signing, no remote', function(done) {
var transaction = new Transaction();
transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij';
transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ';
assert(transaction.complete());
done();
});
it('Complete transaction - untrusted', function(done) { it('Complete transaction - untrusted', function(done) {
var remote = new Remote(); var remote = new Remote();
var transaction = new Transaction(remote); var transaction = new Transaction(remote);
@@ -707,47 +725,20 @@ describe('Transaction', function() {
done(); done();
}); });
it('Sign transaction - with callback', function(done) {
var transaction = new Transaction();
transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij';
transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED';
transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ';
transaction.tx_json.Flags = 0;
transaction.tx_json.Fee = 10;
transaction.tx_json.Sequence = 1;
transaction.tx_json.TransactionType = 'AccountSet';
transaction.sign(function() {
var signature = transaction.tx_json.TxnSignature;
assert.strictEqual(transaction.previousSigningHash, 'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE');
assert(/^[A-Z0-9]+$/.test(signature));
done();
});
});
it('Add transaction ID', function(done) { it('Add transaction ID', function(done) {
var transaction = new Transaction(); var transaction = new Transaction();
var saved = 0;
transaction.on('save', function() {
++saved;
});
transaction.once('save', function() {
setImmediate(function() {
assert.strictEqual(saved, 2);
assert.deepEqual(
transaction.submittedIDs,
[ 'F1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE',
'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE' ]
);
done();
});
});
transaction.addId('D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'); transaction.addId('D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE');
transaction.addId('F1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'); transaction.addId('F1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE');
transaction.addId('F1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'); transaction.addId('F1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE');
assert.deepEqual(
transaction.submittedIDs,
[ 'F1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE',
'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE' ]
);
done();
}); });
it('Find transaction IDs in cache', function(done) { it('Find transaction IDs in cache', function(done) {
@@ -778,9 +769,10 @@ describe('Transaction', function() {
it('Set DestinationTag', function() { it('Set DestinationTag', function() {
var transaction = new Transaction(); var transaction = new Transaction();
transaction.destinationTag();
transaction.destinationTag('tag'); transaction.destinationTag('tag');
assert.strictEqual(transaction.tx_json.DestinationTag, 'tag'); assert.strictEqual(transaction.tx_json.DestinationTag, void(0));
transaction.destinationTag(1);
assert.strictEqual(transaction.tx_json.DestinationTag, 1);
}); });
it('Set InvoiceID', function() { it('Set InvoiceID', function() {
@@ -813,11 +805,46 @@ describe('Transaction', function() {
assert.strictEqual(transaction.tx_json.LastLedgerSequence, void(0)); assert.strictEqual(transaction.tx_json.LastLedgerSequence, void(0));
assert(!transaction._setLastLedger); assert(!transaction._setLastLedger);
transaction.lastLedger(NaN);
assert.strictEqual(transaction.tx_json.LastLedgerSequence, void(0));
assert(!transaction._setLastLedger);
transaction.lastLedger(12); transaction.lastLedger(12);
assert.strictEqual(transaction.tx_json.LastLedgerSequence, 12); assert.strictEqual(transaction.tx_json.LastLedgerSequence, 12);
assert(transaction._setLastLedger); assert(transaction._setLastLedger);
}); });
it('Set Max Fee', function() {
var transaction = new Transaction();
transaction.maxFee('a');
assert(!transaction._setMaxFee);
transaction.maxFee(NaN);
assert(!transaction._setMaxFee);
transaction.maxFee(1000);
assert.strictEqual(transaction._maxFee, 1000);
assert.strictEqual(transaction._setMaxFee, true);
});
it('Set Fixed Fee', function() {
var transaction = new Transaction();
transaction.setFixedFee('a');
assert(!transaction._setFixedFee);
transaction.setFixedFee(-1000);
assert(!transaction._setFixedFee);
transaction.setFixedFee(NaN);
assert(!transaction._setFixedFee);
transaction.setFixedFee(1000);
assert.strictEqual(transaction._setFixedFee, true);
assert.strictEqual(transaction.tx_json.Fee, '1000');
});
it('Rewrite transaction path', function() { it('Rewrite transaction path', function() {
var transaction = new Transaction(); var transaction = new Transaction();
@@ -841,7 +868,7 @@ describe('Transaction', function() {
} }
]; ];
assert.deepEqual(Transaction._pathRewrite(path), [ assert.deepEqual(Transaction._rewritePath(path), [
{ {
account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY',
issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm',
@@ -861,7 +888,9 @@ describe('Transaction', function() {
}); });
it('Rewrite transaction path - invalid path', function() { it('Rewrite transaction path - invalid path', function() {
assert.strictEqual(Transaction._pathRewrite(1), void(0)); assert.throws(function() {
assert.strictEqual(Transaction._rewritePath(1), void(0));
});
}); });
it('Add transaction path', function() { it('Add transaction path', function() {
@@ -970,51 +999,220 @@ describe('Transaction', function() {
it('Set SourceTag', function() { it('Set SourceTag', function() {
var transaction = new Transaction(); var transaction = new Transaction();
transaction.sourceTag('tag'); transaction.sourceTag('tag');
assert.strictEqual(transaction.tx_json.SourceTag, 'tag'); assert.strictEqual(transaction.tx_json.SourceTag, void(0));
transaction.sourceTag(1);
assert.strictEqual(transaction.tx_json.SourceTag, 1);
}); });
it('Set TransferRate', function() { it('Set TransferRate', function() {
var transaction = new Transaction(); var transaction = new Transaction();
transaction.transferRate(1);
assert.throws(function() { assert.strictEqual(transaction.tx_json.TransferRate, void(0));
transaction.transferRate(1);
});
assert.throws(function() {
transaction.transferRate('1');
});
transaction.transferRate(1.5 * 1e9); transaction.transferRate(1.5 * 1e9);
assert.strictEqual(transaction.tx_json.TransferRate, 1.5 * 1e9); assert.strictEqual(transaction.tx_json.TransferRate, 1.5 * 1e9);
}); });
it('Set Flags', function(done) { it('Set Flags', function(done) {
var transaction = new Transaction(); var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment'; transaction.tx_json.TransactionType = 'Payment';
transaction.setFlags(); transaction.setFlags();
assert.strictEqual(transaction.tx_json.Flags, 0); assert.strictEqual(transaction.tx_json.Flags, 0);
transaction.setFlags(1); var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
transaction.setFlags(Transaction.flags.Payment.PartialPayment);
assert.strictEqual(transaction.tx_json.Flags, 131072);
assert.strictEqual(transaction.tx_json.Flags, 1); var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
transaction.setFlags('NoRippleDirect');
assert.strictEqual(transaction.tx_json.Flags, 65536);
transaction.setFlags('PartialPayment'); var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
assert.strictEqual(transaction.tx_json.Flags, 131073); transaction.setFlags('PartialPayment', 'NoRippleDirect');
assert.strictEqual(transaction.tx_json.Flags, 196608);
var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
transaction.setFlags([ 'LimitQuality', 'PartialPayment' ]); transaction.setFlags([ 'LimitQuality', 'PartialPayment' ]);
assert.strictEqual(transaction.tx_json.Flags, 393216);
assert.strictEqual(transaction.tx_json.Flags, 524289); var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
transaction.once('error', function(err) { transaction.once('error', function(err) {
assert.strictEqual(err.result, 'tejInvalidFlag'); assert.strictEqual(err.result, 'tejInvalidFlag');
done(); done();
}); });
transaction.setFlags('asdf');
});
transaction.setFlags('test'); it('Add Memo', function() {
var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
var memoType = 'message';
var memoFormat = 'application/json';
var memoData = {
string: 'value',
bool: true,
integer: 1
};
transaction.addMemo(memoType, memoFormat, memoData);
var expected = [
{
Memo:
{
MemoType: memoType,
MemoFormat: memoFormat,
MemoData: memoData
}
}
];
assert.deepEqual(transaction.tx_json.Memos, expected);
});
it('Add Memo - by object', function() {
var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
var memo = {
memoType: 'type',
memoData: 'data'
};
transaction.addMemo(memo);
var expected = [
{
Memo: {
MemoType: memo.memoType,
MemoData: memo.memoData
}
}
];
assert.deepEqual(transaction.tx_json.Memos, expected);
});
it('Add Memos', function() {
var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
transaction.addMemo('testkey', void(0), 'testvalue');
transaction.addMemo('testkey2', void(0), 'testvalue2');
transaction.addMemo('testkey3', 'text/html');
transaction.addMemo(void(0), void(0), 'testvalue4');
transaction.addMemo('testkey4', 'text/html', '<html>');
var expected = [
{ Memo: {
MemoType: 'testkey',
MemoData: 'testvalue'
}},
{ Memo: {
MemoType: 'testkey2',
MemoData: 'testvalue2'
}},
{ Memo: {
MemoType: 'testkey3',
MemoFormat: 'text/html'
}},
{ Memo: {
MemoData: 'testvalue4'
}},
{ Memo: {
MemoType: 'testkey4',
MemoFormat: 'text/html',
MemoData: '<html>'
}}
];
assert.deepEqual(transaction.tx_json.Memos, expected);
});
it('Add Memo - invalid MemoType', function() {
var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
assert.throws(function() {
transaction.addMemo(1);
}, /^Error: MemoType must be a string$/);
});
it('Add Memo - invalid ASCII MemoType', function() {
var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
assert.throws(function() {
transaction.addMemo('한국어');
}, /^Error: MemoType must be valid ASCII$/);
});
it('Add Memo - invalid MemoFormat', function() {
var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
assert.throws(function() {
transaction.addMemo(void(0), 1);
}, /^Error: MemoFormat must be a string$/);
});
it('Add Memo - invalid ASCII MemoFormat', function() {
var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
assert.throws(function() {
transaction.addMemo(void(0), 'России');
}, /^Error: MemoFormat must be valid ASCII$/);
});
it('Add Memo - MemoData string', function() {
var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
transaction.addMemo({memoData:'some_string'});
assert.deepEqual(transaction.tx_json.Memos, [
{
Memo: {
MemoData: 'some_string'
}
}
]);
});
it('Add Memo - MemoData complex object', function() {
var transaction = new Transaction();
transaction.tx_json.TransactionType = 'Payment';
var memo = {
memoData: {
string: 'string',
int: 1,
array: [
{
string: 'string'
}
],
object: {
string: 'string'
}
}
};
transaction.addMemo(memo);
assert.deepEqual(transaction.tx_json.Memos, [
{
Memo: {
MemoData: memo.memoData
}
}
]);
}); });
it('Construct AccountSet transaction', function() { it('Construct AccountSet transaction', function() {
@@ -1191,7 +1389,7 @@ describe('Transaction', function() {
var bid = '1/USD/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'; var bid = '1/USD/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm';
var ask = '1/EUR/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'; var ask = '1/EUR/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm';
assert.throws(function() { assert.throws(function() {
var transaction = new Transaction().offerCreate('xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', bid, ask); new Transaction().offerCreate('xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', bid, ask);
}); });
}); });
@@ -1224,13 +1422,13 @@ describe('Transaction', function() {
it('Construct SetRegularKey transaction - invalid account', function() { it('Construct SetRegularKey transaction - invalid account', function() {
assert.throws(function() { assert.throws(function() {
var transaction = new Transaction().setRegularKey('xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'); new Transaction().setRegularKey('xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe');
}); });
}); });
it('Construct SetRegularKey transaction - invalid regularKey', function() { it('Construct SetRegularKey transaction - invalid regularKey', function() {
assert.throws(function() { assert.throws(function() {
var transaction = new Transaction().setRegularKey('rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 'xr36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'); new Transaction().setRegularKey('rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 'xr36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe');
}); });
}); });
@@ -1456,48 +1654,88 @@ describe('Transaction', function() {
transaction.submit(submitCallback); transaction.submit(submitCallback);
}); });
it('Submit transaction - invalid account', function(done) { it('Submit transaction - submission error, no remote', function(done) {
var remote = new Remote(); var transaction = new Transaction();
var transaction = new Transaction(remote).accountSet('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe');
transaction.tx_json.Account += 'z'; transaction.once('error', function(error) {
assert(error);
transaction.once('error', function(err) { assert.strictEqual(error.message, 'No remote found');
assert.strictEqual(err.result, 'tejInvalidAccount');
done(); done();
}); });
transaction.submit(); transaction.submit();
}); });
it.skip('Abort submission', function(done) { it('Submit transaction - invalid account', function(done) {
var remote = new Remote(); var remote = new Remote();
var transaction = new Transaction(remote).accountSet('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'); assert.throws(function() {
var account = remote.addAccount('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'); var transaction = new Transaction(remote).accountSet('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWeZ');
});
done();
});
it('Abort submission on presubmit', function(done) {
var remote = new Remote();
remote.setSecret('rJaT8TafQfYJqDm8aC5n3Yx5yWEL2Ery79', 'snPwFATthTkKnGjEW73q3TL4yci1Q');
var server = new Server(remote, 'wss://s1.ripple.com:443');
server._computeFee = function() { return '12'; };
server._connected = true;
remote._servers.push(server);
remote._connected = true;
remote._ledger_current_index = 1;
var transaction = new Transaction(remote).accountSet('rJaT8TafQfYJqDm8aC5n3Yx5yWEL2Ery79');
var account = remote.account('rJaT8TafQfYJqDm8aC5n3Yx5yWEL2Ery79');
account._transactionManager._nextSequence = 1; account._transactionManager._nextSequence = 1;
account._transactionManager._request = function(tx) { transaction.once('presubmit', function() {
setTimeout(function() { transaction.abort();
tx.emit('success', { }); });
}, 20);
};
transaction.complete = function() { transaction.submit(function(err, res) {
return this;
};
function submitCallback(err, res) {
setImmediate(function() { setImmediate(function() {
assert(err); assert(err);
assert.strictEqual(err.result, 'tejAbort'); assert.strictEqual(err.result, 'tejAbort');
done(); done();
}); });
}; });
});
transaction.submit(submitCallback); it('Get min ledger', function() {
transaction.abort(); var queue = new TransactionQueue();
// Randomized submit indexes
[
28093,
456944,
347213,
165662,
729760,
808990,
927393,
925550,
872298,
543305
]
.forEach(function(index){
var tx = new Transaction();
tx.initialSubmitIndex = index;
queue.push(tx);
});
// Pending queue sorted by submit index
var sorted = queue._queue.slice().sort(function(a, b) {
return a.initialSubmitIndex - b.initialSubmitIndex;
});
sorted.forEach(function(tx){
assert.strictEqual(queue.getMinLedger(), tx.initialSubmitIndex);
queue.remove(tx);
});
}); });
}); });
// vim:sw=2:sts=2:ts=8:et //vim:sw=2:sts=2:ts=8:et

View File

@@ -20,6 +20,7 @@ var exampleData = {
email_token : '77825040-9096-4695-9cbc-76720f6a8649', email_token : '77825040-9096-4695-9cbc-76720f6a8649',
activateLink : 'https://staging.ripple.com/client/#/register/activate/', activateLink : 'https://staging.ripple.com/client/#/register/activate/',
device_id : "ac1b6f6dbca98190eb9687ba06f0e066", device_id : "ac1b6f6dbca98190eb9687ba06f0e066",
identity_id : "17fddb71-a5c2-44ce-8b50-4b381339d4f2",
blob: { blob: {
url: 'https://id.staging.ripple.com', url: 'https://id.staging.ripple.com',
id: 'ef203d3e76552c0592384f909e6f61f1d1f02f61f07643ce015d8b0c9710dd2f', id: 'ef203d3e76552c0592384f909e6f61f1d1f02f61f07643ce015d8b0c9710dd2f',
@@ -103,12 +104,29 @@ var recoverRes = {
result: 'success' result: 'success'
} }
} }
var getProfileRes = {
"result":"success",
"addresses":[],
"attributes":[{
"attribute_id":"4034e477-ffc9-48c4-bcbc-058293f081d8",
"identity_id":"17fddb71-a5c2-44ce-8b50-4b381339d4f2",
"name":"email",
"type":"default",
"domain":null,
"value":"example@example.com",
"visibility":"public",
"updated":null
}
]
};
var blob = new Blob(); var blob = new Blob();
blob.url = exampleData.blob.url; blob.url = exampleData.blob.url;
blob.id = exampleData.blob.id; blob.id = exampleData.blob.id;
blob.device_id = exampleData.device_id; blob.device_id = exampleData.device_id;
blob.key = exampleData.blob.key; blob.key = exampleData.blob.key;
blob.identity_id = exampleData.blob.identity_id;
blob.data = exampleData.blob.data; blob.data = exampleData.blob.data;
blob.revision = exampleData.blob.data.revision; blob.revision = exampleData.blob.data.revision;
@@ -133,14 +151,12 @@ var mockDelete;
if (!online) { if (!online) {
mockRippleTxt = nock('https://ripple.com') mockRippleTxt = nock('https://ripple.com')
.persist()
.get('/ripple.txt') .get('/ripple.txt')
.reply(200, rippleTxtRes, { .reply(200, rippleTxtRes, {
'Content-Type': 'text/plain' 'Content-Type': 'text/plain'
}); });
mockRippleTxt2 = nock('https://' + exampleData.domain) mockRippleTxt2 = nock('https://' + exampleData.domain)
.persist()
.get('/ripple.txt') .get('/ripple.txt')
.reply(200, rippleTxtRes, { .reply(200, rippleTxtRes, {
'Content-Type': 'text/plain' 'Content-Type': 'text/plain'
@@ -153,21 +169,21 @@ if (!online) {
'Content-Type': 'text/plain' 'Content-Type': 'text/plain'
}); });
mockRegister = nock('https://id.staging.ripple.com').persist(); mockRegister = nock('https://id.staging.ripple.com');
mockRegister.filteringPath(/(v1\/user\?signature(.+))/g, 'register/') mockRegister.filteringPath(/(v1\/user\?signature(.+))/g, 'register/')
.post('/register/') .post('/register/')
.reply(200, { result: 'error', message: 'User already exists' }, { .reply(200, { result: 'error', message: 'User already exists' }, {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}); });
mockDelete = nock('https://id.staging.ripple.com').persist(); mockDelete = nock('https://id.staging.ripple.com');
mockDelete.filteringPath(/(v1\/user\/(.+))/g, 'delete/') mockDelete.filteringPath(/(v1\/user\/(.+))/g, 'delete/')
.delete('/delete/') .delete('/delete/')
.reply(200, { result: 'success' }, { .reply(200, { result: 'success' }, {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}); });
mockBlob = nock('https://id.staging.ripple.com').persist(); mockBlob = nock('https://id.staging.ripple.com');
mockBlob.get('/v1/authinfo?domain=' + exampleData.domain + '&username=' + exampleData.username.toLowerCase()) mockBlob.get('/v1/authinfo?domain=' + exampleData.domain + '&username=' + exampleData.username.toLowerCase())
.reply(200, JSON.stringify(authInfoRes.body), { .reply(200, JSON.stringify(authInfoRes.body), {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -185,47 +201,40 @@ if (!online) {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}); });
mockRename = nock('https://id.staging.ripple.com/v1/user/').persist(); mockRename = nock('https://id.staging.ripple.com/v1/user/');
mockRename.filteringPath(/((.+)\/rename(.+))/g, 'rename/') mockRename.filteringPath(/((.+)\/rename(.+))/g, 'rename/')
.post('rename/') .post('rename/')
.reply(200, {result:'success',message:'rename'}, { .reply(200, {result:'success',message:'rename'}, {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}); });
mockUpdate = nock('https://id.staging.ripple.com/v1/user/').persist(); mockUpdate = nock('https://id.staging.ripple.com/v1/user/');
mockUpdate.filteringPath(/((.+)\/update(.+))/g, 'update/') mockUpdate.filteringPath(/((.+)\/updatekeys(.+))/g, 'update/')
.post('update/') .post('update/')
.reply(200, {result:'success',message:'updateKeys'}, { .reply(200, {result:'success',message:'updateKeys'}, {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}); });
mockRecover = nock('https://id.staging.ripple.com/').persist(); mockRecover = nock('https://id.staging.ripple.com/')
mockRecover.filteringPath(/((.+)user\/recov\/(.+))/g, 'recov/') mockRecover.filteringPath(/((.+)user\/recov\/(.+))/g, 'recov/')
.get('recov/') .get('recov/')
.reply(200, recoverRes.body, { .reply(200, recoverRes.body, {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}); });
mockVerify = nock('https://id.staging.ripple.com/v1/user/').persist(); mockVerify = nock('https://id.staging.ripple.com/v1/user/');
mockVerify.filteringPath(/((.+)\/verify(.+))/g, 'verify/') mockVerify.filteringPath(/((.+)\/verify(.+))/g, 'verify/')
.get('verify/') .get('verify/')
.reply(200, {result:'error', message:'invalid token'}, { .reply(200, {result:'error', message:'invalid token'}, {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}); });
mockEmail = nock('https://id.staging.ripple.com/v1/user').persist(); mockEmail = nock('https://id.staging.ripple.com/v1/user');
mockEmail.filteringPath(/((.+)\/email(.+))/g, 'email/') mockEmail.filteringPath(/((.+)\/email(.+))/g, 'email/')
.post('email/') .post('email/')
.reply(200, {result:'success'}, { .reply(200, {result:'success'}, {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}); });
mockProfile = nock('https://id.staging.ripple.com/v1/user').persist();
mockProfile.filteringPath(/((.+)\/profile(.+))/g, 'profile/')
.post('profile/')
.reply(200, {result:'success'}, {
'Content-Type': 'application/json'
});
} }
describe('Ripple Txt', function () { describe('Ripple Txt', function () {
@@ -419,6 +428,7 @@ describe('VaultClient', function () {
}); });
}); });
/*
describe('#updateProfile', function () { describe('#updateProfile', function () {
it('should update profile parameters associated with a blob', function (done) { it('should update profile parameters associated with a blob', function (done) {
this.timeout(10000); this.timeout(10000);
@@ -442,7 +452,7 @@ describe('VaultClient', function () {
}); });
}); });
}); });
*/
}); });
@@ -693,7 +703,95 @@ describe('Blob', function () {
}); });
}); });
}); });
describe('identityVault', function() {
it('#identity - Get Attestation', function (done) {
var options = {
url : blob.url,
auth_secret : blob.data.auth_secret,
blob_id : blob.id,
};
options.type = 'identity';
nock('https://id.staging.ripple.com')
.filteringPath(/(v1\/attestation\/identity(.+))/g, '')
.post('/')
.reply(200, {
result: 'success',
status: 'verified',
attestation: 'eyJ6IjoieiJ9.eyJ6IjoieiJ9.sig',
blinded:'eyJ6IjoieiJ9.eyJ6IjoieiJ9.sig'
}, {'Content-Type': 'application/json'});
client.getAttestation(options, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.result, 'success');
assert.strictEqual(typeof resp.attestation, 'string');
assert.strictEqual(typeof resp.blinded, 'string');
assert.deepEqual(resp.decoded, {"header":{"z":"z"},"payload":{"z":"z"},"signature":"sig"})
done();
});
});
it('#identity - Update Attestation', function (done) {
var options = {
url : blob.url,
auth_secret : blob.data.auth_secret,
blob_id : blob.id,
};
options.type = 'identity';
nock('https://id.staging.ripple.com')
.filteringPath(/(v1\/attestation\/identity\/update(.+))/g, '')
.post('/')
.reply(200, {
result: 'success',
status: 'verified',
attestation: 'eyJ6IjoieiJ9.eyJ6IjoieiJ9.sig',
blinded:'eyJ6IjoieiJ9.eyJ6IjoieiJ9.sig'
}, {'Content-Type': 'application/json'});
client.updateAttestation(options, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.result, 'success');
assert.strictEqual(typeof resp.attestation, 'string');
assert.strictEqual(typeof resp.blinded, 'string');
assert.deepEqual(resp.decoded, {"header":{"z":"z"},"payload":{"z":"z"},"signature":"sig"})
done();
});
});
it('#identity - Get Attestation Summary', function (done) {
var options = {
url : blob.url,
auth_secret : blob.data.auth_secret,
blob_id : blob.id,
};
nock('https://id.staging.ripple.com')
.filteringPath(/(v1\/attestation\/summary(.+))/g, '')
.get('/')
.reply(200, {
result: 'success',
attestation: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjY2ZGI3MzgxIn0%3D.eyJwcm9maWxlX3ZlcmlmaWVkIjpmYWxzZSwiaWRlbnRpdHlfdmVyaWZpZWQiOmZhbHNlLCJpc3MiOiJodHRwczovL2lkLnJpcHBsZS5jb20iLCJzdWIiOiIwNDMzNTA0ZS0yYTRmLTQ1NjktODQwMi1lYWI2YTU0YTgzYjUiLCJleHAiOjE0MTI4MTc2NjksImlhdCI6MTQxMjgxNTgwOX0%3D.Jt14Y2TsM7fKqGWn0j16cPldlYqRr7%2F2dptBsdZuZhRGRTREO4TSpZZhBaU95WL3M9eXIfaoSs8f2pTOa%2BBGAYHZSZK4%2FLqeWdDH8zz8Bx9YFqGije1KmHQR%2FeoWSp1GTEfcq5Oho4nSHozHhGNN8IrDkl8woMvWb%2FE1938Y5Zl2vyv7wjlNUF4ND33XWzJkvQjzIK15uYfaB%2FUIsNW32udfHAdkigesdMDNm%2BRGBqHMDZeAMdVxzrDzE3m8oWKDMJXbcaLmk75COfJrLWYiZCHd7VcReyPEZegwEucetZJ9uDnoBcvw0%2B6hIRmjTN6Gy1eeBoJaiDYsWuOwInbIlw%3D%3D',
}, {'Content-Type': 'application/json'});
client.getAttestationSummary(options, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.result, 'success');
assert.strictEqual(typeof resp.attestation, 'string');
assert.strictEqual(typeof resp.decoded.header, 'object');
assert.strictEqual(typeof resp.decoded.payload, 'object');
assert.strictEqual(typeof resp.decoded.signature, 'string');
done();
});
});
});
//only do these offline //only do these offline
if (!online) { if (!online) {
@@ -709,7 +807,7 @@ describe('Blob', function () {
}); });
it('#2FA_get2FA', function (done) { it('#2FA_get2FA', function (done) {
blob.get2FA(exampleData.masterkey, function(err, resp) { blob.get2FA(function(err, resp) {
assert.ifError(err); assert.ifError(err);
assert.strictEqual(typeof resp, 'object'); assert.strictEqual(typeof resp, 'object');
assert.strictEqual(typeof resp.result, 'string'); assert.strictEqual(typeof resp.result, 'string');