Compare commits

...

556 Commits

Author SHA1 Message Date
wltsmrz
2267e599c0 Update HISTORY 2014-03-18 17:13:47 -07:00
wltsmrz
687bcc8539 Add HISTORY.md 2014-03-18 16:35:01 -07:00
wltsmrz
e64dba839c Bump version 2014-03-18 16:07:45 -07:00
wltsmrz
a651b6154d Properly choose ledger identifier in requestTransactionEntry 2014-03-18 12:00:54 -07:00
wltsmrz
4b5e75a8ad Add initial submit index and last ledger sequence to transaction summary 2014-03-18 11:26:00 -07:00
wltsmrz
bdd24bada9 Use binary account_tx on remote reconnect 2014-03-17 13:33:06 -07:00
wltsmrz
6e4a9179c1 Generate transaction hash in account_tx automatic binary handling 2014-03-17 13:21:54 -07:00
wltsmrz
b0c38ff566 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2014-03-15 22:02:42 -07:00
wltsmrz
e97a3966c1 Automatically parse binary account_tx response 2014-03-15 22:02:31 -07:00
Nicholas Dudfield
d0aa86bf97 Updated bundled sjcl.js to include typo fix from 007c2e7e5c 2014-03-15 14:45:23 +07:00
wltsmrz
b17eb8ed0d Check missing secret first in tx.complete 2014-03-13 11:34:07 -07:00
wltsmrz
7dcfa0a232 Set tx.finalized before changing state to validated/failed 2014-03-13 11:32:20 -07:00
wltsmrz
05c466bebe Add switch break 2014-03-11 16:36:53 -07:00
wltsmrz
0f7c80d76a Resubmit transactions failed with telINSUF_FEE_P 2014-03-11 16:35:48 -07:00
wltsmrz
99599ba673 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2014-03-10 16:29:08 -07:00
wltsmrz
85f75b4543 Emit tejSecretUnknown if secret is missing during signing, enhancements for ripple-rest 2014-03-10 16:28:41 -07:00
Stefan Thomas
37a22186ca Add memo field binary types. 2014-03-10 03:37:13 -07:00
sublimator
007c2e7e5c Fix typo 2014-03-08 23:37:25 +07:00
wltsmrz
a7581b1ec8 Whitelist build/sjcl 2014-03-07 14:52:25 -08:00
wltsmrz
06c662cc14 Add build/sjcl, remove grunt postinstall step 2014-03-07 14:45:38 -08:00
wltsmrz
7feafaa320 Add build, test, Gruntfile, Makefile to npm files 2014-03-07 13:53:12 -08:00
wltsmrz
1c06baf5db Add grunt postinstall script 2014-03-07 13:31:44 -08:00
wltsmrz
a188ae6ace Cleanup 2014-03-07 13:31:37 -08:00
wltsmrz
c0db5e251a Add transaction.lastLedger, whitelist type_hex path property 2014-03-07 11:55:46 -08:00
wltsmrz
bc427204c6 Cleanup 2014-03-07 11:54:02 -08:00
wltsmrz
976de00242 Update remote init test 2014-03-03 15:27:38 -08:00
wltsmrz
a098b0cf02 Fix trace validation 2014-03-03 15:08:38 -08:00
wltsmrz
70d28fc15e Fix remote storage validation 2014-03-03 13:20:47 -08:00
wltsmrz
815eed427f Validate remote configuration. Fixes #58 2014-03-03 13:10:56 -08:00
wltsmrz
55e74671a7 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2014-03-03 12:09:16 -08:00
wltsmrz
e595291c95 Merge pull request #62 from sublimator/develop
Set VER_ACCOUNT_ID on address returned by KeyPair.get_address
2014-03-03 12:09:10 -08:00
wltsmrz
47db68ff45 Use lru-cache API 2014-03-03 11:55:57 -08:00
Nicholas Dudfield
e227a541e7 Set VER_ACCOUNT_ID on address returned by keypair
Just a decent default, client code can still set_version before base58 encoding.
2014-03-03 11:09:15 +07:00
wltsmrz
7a88165e16 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2014-02-28 16:27:24 -08:00
wltsmrz
5ed148af75 Persistent transactions interface (Gateway REST API) 2014-02-28 16:27:09 -08:00
Stefan Thomas
1329de5344 Log: Cleanup some unused code. 2014-02-27 23:39:28 -08:00
Stefan Thomas
a003d261ea Log: New logging system.
Right now, all I've done is move the logging function from utils to its own
file. I've also refactored it and created some classes.

Next step would be to somehow get rid of the trace setting. The Remote class
should have nothing to do with logging settings. Perhaps we can keep the setting
for backwards compatibility only, but the main way to turn logging on/off or
filter logging messages should be through the Log class.
2014-02-27 23:38:45 -08:00
wltsmrz
37065123e5 Catch max fee exceeded on fee adjust 2014-02-27 15:13:15 -08:00
wltsmrz
44dd0ca4a3 Merge pull request #60 from sublimator/develop
Proposed fix for #51 and #57
2014-02-27 12:14:16 -08:00
wltsmrz
15304237d1 Don't automatically resubmit tefMAX_LEDGER, maintain transaction state, add transaction.iff(fn, callback) to be executed on each submission 2014-02-26 17:31:59 -08:00
Nicholas Dudfield
acfc1bbb8c Proposed fix for #51 and #57 2014-02-26 13:57:48 +07:00
wltsmrz
75a732e0ad Bump submitIndex on resubmit 2014-02-24 13:43:20 -08:00
wltsmrz
a46bff3c5e Bump version 2014-02-20 15:49:13 -08:00
wltsmrz
8fcb694f1d Fix account requests with object argument 2014-02-20 15:48:12 -08:00
wltsmrz
ef758b17b1 Bump LRU cache max size to 100 2014-02-20 14:23:17 -08:00
wltsmrz
b9646c51b8 Automatically resubmit on tefMAX_LEDGER 2014-02-19 16:08:15 -08:00
wltsmrz
e2f59ce975 Bump ripple-lib version 2014-02-19 14:41:28 -08:00
wltsmrz
c3ceba1f05 Cleanup 2014-02-19 14:06:55 -08:00
wltsmrz
573b38cc19 Add lru-cache dependency; make remote._received_tx an lru-cache with specified max length 2014-02-19 13:00:38 -08:00
wltsmrz
5ca5c73c34 Clear caches when queue empties 2014-02-19 12:21:55 -08:00
wltsmrz
5a15c5b1d5 Set LastLedgerSequence to current_ledger_index + 8 on outgoing transactions 2014-02-19 11:10:05 -08:00
wltsmrz
c60097f8c5 Change optional ping interval units to seconds 2014-02-18 16:41:24 -08:00
wltsmrz
7f13a71252 Use account_tx outbound filter, only query account_tx if the pending queue is non-empty 2014-02-18 16:33:31 -08:00
sublimator
5c8550b364 Update serializedtypes-test.js 2014-02-15 09:31:59 +07:00
wltsmrz
f27be38ca9 Add automatic ping option 2014-02-10 13:25:20 -08:00
Stefan Thomas
aed6b7754e Amount: Support passing demurrage currencies to Amount#parse_human.
If a reference date is provided as an option, the method will apply the
demurrage/interest rate compounded to that date.
2014-02-09 23:07:04 -08:00
Stefan Thomas
0772ffb6ed Currency: Behavior change for to_json for demurrage currencies.
Currency#to_json should output a format that can be successfully converted back
to a Currency object via from_json. So for demurrage currencies it should output
the full hex currency code.

Currency#to_human on the other hand should always print something a human will
understand. For demurrage currencies we'll print the three-letter code for now.
2014-02-09 23:07:04 -08:00
Vahe Hovhannisyan
37e6d95cbc Index: Add UInt256, change UInt160 and Seed paths. 2014-02-07 15:49:09 -08:00
Vahe Hovhannisyan
644b611b9b Transaction: Comment fix. 2014-02-07 14:21:59 -08:00
sublimator
b21be326fe SLE TODO for binformat 2014-02-05 23:15:00 +07:00
Stefan Thomas
3d9cea62ff Add new fields to binformat. 2014-02-04 22:21:43 -08:00
Stefan Thomas
a15da9ade1 Remove redundant information in binformat.js. 2014-02-04 21:38:25 -08:00
Stefan Thomas
a07b76a28c Move field type maps to binformat.js. 2014-02-04 21:38:20 -08:00
Stefan Thomas
372c508911 Improve error message for unknown field 2014-02-04 15:40:50 -08:00
Stefan Thomas
257e9da563 Fix reference to Transaction#parseJson. 2014-02-04 15:40:10 -08:00
Stefan Thomas
cdfb7989b0 Better error message for unknown fields. 2014-02-03 18:11:04 -08:00
Nicholas Dudfield
b01c15847c Fix binary serialization of Vector256 2014-02-01 11:02:10 +07:00
Nicholas Dudfield
23528293e0 Fix binary parsing of Vector256 2014-02-01 10:46:04 +07:00
wltsmrz
430dfef23d Bump ripple-lib version 2014-01-28 16:32:14 -08:00
wltsmrz
215a3f1669 Interface for storing external pending transaction queue 2014-01-28 15:03:16 -08:00
Stefan Thomas
f678f47155 Amount: Full demurrage support. 2014-01-25 11:31:56 -08:00
Stefan Thomas
fa07601a2a UInt: Add UInt#_update for creating subclasses that have dependent state. 2014-01-25 11:30:58 -08:00
Stefan Thomas
0d4357232c Fix grunt watch. Enable caching, only compile debug version. 2014-01-25 11:25:21 -08:00
Stefan Thomas
014514a4da Add a utility function for converting back from JS time to Ripple epoch. 2014-01-25 00:18:57 -08:00
Stefan Thomas
86c35d7d9c Add a new simple test for XRP IOUs in PathSet. 2014-01-24 05:25:30 -08:00
Stefan Thomas
9f76907f51 Currency: Add support for complex currencies. (UInt160)
This patch might regress the performance of the Currency class and by extension
the Amount class. Since Amount is on a lot of hot paths in the client we should
make sure this isn't a major problem.

As for compatibility, this patch is a major change, but it should maintain the
public interface very well, which the exception of some strange edge cases (e.g.
Currency.from_json(1337)), which weren't well-defined before anyway. Any code
that accesses _value directly (shame on you!) will need to be fixed. There
aren't any such references in ripple-client or the rippled test suite, so I
think we're looking pretty good.
2014-01-24 05:24:46 -08:00
sublimator
4e67167394 Fix usage in decode_binary.js 2014-01-22 12:49:36 +07:00
wltsmrz
292b6825fc Merge pull request #48 from miracle2k/signing_hash
Fix signingHash calls; make rsign output pre-sign hash.
2014-01-21 16:54:06 -08:00
wltsmrz
aed87f7437 Merge pull request #47 from miracle2k/decode_script
Add utility script to decode binary blobs.
2014-01-21 16:53:58 -08:00
Michael Elsdörfer
c95d976e84 Fix signingHash calls; make rsign output pre-sign hash. 2014-01-19 16:04:38 +01:00
Michael Elsdörfer
7bbab0759a Add utility script to decode binary blobs. 2014-01-19 16:01:14 +01:00
sublimator
2396a53b03 Make order book subscription work 2014-01-19 15:58:23 +07:00
wltsmrz
43ee82af57 Remove extraneous timing 2014-01-13 15:42:55 -08:00
wltsmrz
3308e0ea92 Fix merge conflict 2014-01-13 15:41:20 -08:00
wltsmrz
61a6db1641 Cleanup transactionqueue 2014-01-13 15:38:40 -08:00
wltsmrz
43eb4c3bfe Prevent duplicate 'transaction' events from Remote 2014-01-13 11:59:27 -08:00
wltsmrz
8538b3ccf2 Merge pull request #43 from sublimator/develop
Disable RTS for local signing
2014-01-13 11:27:27 -08:00
Nicholas Dudfield
f9bb7094e9 Disable transaction resubmission when remote signing. Throw error when attempt to build_path when using local_signing 2014-01-09 13:48:03 +07:00
wltsmrz
54b7099d01 Fix orderbook subscribe 2014-01-08 15:47:33 -08:00
wltsmrz
4ce04a5c90 requestTransactionEntry arguments 2014-01-08 15:13:31 -08:00
wltsmrz
19e29722dc Fix orderbook unsubscribe 2014-01-08 15:13:18 -08:00
wltsmrz
99351aa2dc Add Transaction.transactionManager getter for convenience 2014-01-07 10:28:55 -08:00
wltsmrz
ef6aca8d12 Don't store complete transactions in TransactionManager sequence cache 2014-01-07 10:18:33 -08:00
wltsmrz
b44f22c87f Emit 'signed' event when transaction is resigned 2014-01-07 10:17:58 -08:00
wltsmrz
ca653c42f1 Update license 2014-01-07 03:14:07 -08:00
wltsmrz
5544752dcd Update license 2014-01-07 03:12:06 -08:00
wltsmrz
4082bd2a0a Fix ledger index argument to transactionEntry, add flexibility to remote.transaction 2014-01-07 01:34:04 -08:00
wltsmrz
626164a8ab Fix requestTransactionEntry arguments, add flexibility to remote.transaction 2014-01-07 01:23:54 -08:00
wltsmrz
848c203b81 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2014-01-06 09:42:57 -08:00
wltsmrz
9f92a8dcce Typo. Fixes #39 2014-01-06 09:40:52 -08:00
Stefan Thomas
c8bb6e2a6a Fix last commit. 2014-01-05 14:41:11 -08:00
Stefan Thomas
f135021c52 Fix endless loop when creating an Account object for a non-existent account. 2014-01-05 14:09:20 -08:00
wltsmrz
f7dc3b9fa1 Merge pull request #38 from sublimator/develop
Add `SerializedObject.check_no_missing_fields`, fixing regression in checking for `binformat.REQUIRED` fields
2014-01-04 21:22:38 -08:00
Nicholas Dudfield
967317f37e Add SerializedObject.check_no_missing_fields 2014-01-05 11:36:35 +07:00
wltsmrz
b5d29533ee Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2014-01-03 20:13:35 -08:00
wltsmrz
a5c1dd1089 Use server.computeFee 2014-01-03 20:13:04 -08:00
wltsmrz
fd614ce0df Separate Server fee calculation, select fee-optimal servers while submitting a transaction 2014-01-03 20:08:24 -08:00
Stefan Thomas
eaf01312e4 More general secret auto-fill and ability to override with Transaction#secret. 2014-01-02 18:52:57 -08:00
wltsmrz
5c3f3ff0f1 server_status rippled event doesn't exist 2013-12-31 14:36:15 -08:00
wltsmrz
4a2515a6b2 Update license 2013-12-31 14:36:04 -08:00
wltsmrz
c3a8d37e1c Refactor 2013-12-31 12:31:02 -08:00
wltsmrz
66af0a0b8e Object logging 2013-12-31 11:31:34 -08:00
wltsmrz
1a093c165e Cleanup 2013-12-30 17:48:33 -08:00
wltsmrz
7182c85f11 API convert additional classes 2013-12-30 15:26:06 -08:00
wltsmrz
eed4a932bd Update bin/validate_address 2013-12-30 15:17:52 -08:00
wltsmrz
b5f4fdf918 Remove validate_account (it exists as bin/validate_address.js) 2013-12-30 15:14:53 -08:00
wltsmrz
ce9e2c9539 Allow accounts_proposed stream, check transaction.validated before finalizing transactions 2013-12-30 15:12:15 -08:00
wltsmrz
1cf0fbbde4 Add accounts_proposed setters, default rtAccounts to accounts_proposed 2013-12-30 15:11:12 -08:00
wltsmrz
10d5725919 Fix merge conflicts 2013-12-30 13:28:15 -08:00
wltsmrz
e9ec3ec3ef Merge pull request #36 from sublimator/develop
Fix bug in (redundant) check for transaction result immediately before resubmission
2013-12-28 22:07:54 -08:00
Nicholas Dudfield
95c7acb210 Fix bug in (redundant) check for transaction result immediately before resubmission 2013-12-29 11:55:54 +07:00
Stefan Thomas
65b035267c Remove comment that doesn't make sense in the new context. 2013-12-27 16:55:02 -08:00
Vahe Hovhannisyan
f10bc6718c Account.prototype.line: add a check. 2013-12-27 14:31:49 -08:00
Vahe Hovhannisyan
5229a3d507 Add Account.prototype.line. Retrieve this account's single trust line. 2013-12-27 11:57:29 -08:00
wltsmrz
e7b6050b82 Add bin/validate_address 2013-12-24 17:19:25 -08:00
Stefan Thomas
38a6e8b009 Fixing OrderBook#_subscribe (WIP). 2013-12-24 14:57:39 -08:00
Stefan Thomas
96b46d2394 Added Request#addBook. 2013-12-24 14:57:06 -08:00
wltsmrz
d401a70a2a Logging objects in browser 2013-12-24 13:20:12 -08:00
wltsmrz
9ad5337e7b Cleanup 2013-12-24 13:19:58 -08:00
wltsmrz
03c7b6b2aa Validate ledgerClosed messages and reject past ledgers 2013-12-23 18:06:36 -08:00
wltsmrz
94e5c3ddf1 Use constant Ripple-time offset 2013-12-23 14:08:52 -08:00
wltsmrz
caac293be5 Server logging 2013-12-23 11:53:52 -08:00
wltsmrz
faae634853 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-12-23 11:49:30 -08:00
wltsmrz
627acdbfde Update in-browser logging to accommodate n-args 2013-12-23 11:48:39 -08:00
Stefan Thomas
a7532b5f55 Disable ripple-lib in Safari iOS < 5 and Safari Mac < 6.
Actively disabling support for these browsers allows us to catch and report the
problem rather than trying to connect and failing for unknown reasons.
2013-12-23 10:35:28 -08:00
Stefan Thomas
9596787cad Throw specific exception when there is no websocket support detected. 2013-12-23 10:31:50 -08:00
wltsmrz
6cdeacdb90 Remote: Only log set_state when the state will actually change. Only set state online when connection_count === 1 2013-12-19 17:46:57 -08:00
wltsmrz
1b6945f74d Prevent NoOp transaction recycling 2013-12-19 17:45:20 -08:00
wltsmrz
675a599876 Log server URL for enhanced debugging 2013-12-17 13:22:49 -08:00
wltsmrz
94c57bc0d8 Cleanup 2013-12-17 12:56:53 -08:00
wltsmrz
667919cea1 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-12-17 12:36:05 -08:00
wltsmrz
9870761e32 Pass arbitrary number of arguments to utils.logObject 2013-12-17 12:34:41 -08:00
wltsmrz
8ef6fdea14 Merge pull request #35 from sublimator/develop
Keep track of all transactionIDs for a given Transaction that go on the network
2013-12-17 12:26:38 -08:00
wltsmrz
5c911367b7 Merge pull request #34 from chgwei/develop
Improvement `remote.request_book_offers` parameters passing
2013-12-16 20:20:44 -08:00
Nicholas Dudfield
e34cf6ebcf Remove unused methods from TransactionQueue 2013-12-15 10:09:54 +07:00
Nicholas Dudfield
620a33b7bd Keep track of all transactionIDs for a given Transaction that go on the network 2013-12-15 10:01:29 +07:00
Cheng wei
e65bec78a1 Improvement remote.request_book_offers parameters passing
As the example  in doc reference:
var request = remote.request_book_offers({
gets: {
'currency':'XRP'
},
pays: {
'currency':'USD',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
}
});
It will raise `TypeError: Cannot read property 'currency' of
undefined`. Fixed issue with parsing parameters
2013-12-12 02:30:24 +08:00
Stefan Thomas
d33c8e46af Finalized transactions should seize all activity. 2013-12-09 18:55:53 -08:00
Stefan Thomas
472a5091ae Add grunt build step to npm test. Should fix Travis. 2013-12-09 18:44:40 -08:00
Vahe Hovhannisyan
ff5e9fcaa9 Transaction: semicolon, comma fixes. 2013-12-09 12:36:14 -08:00
wltsmrz
4a027bdcdf Add remote.disconnect callback 2013-12-05 17:10:04 -08:00
wltsmrz
dbdfc262b6 Only one 'submitted' event will be emitted per transaction submission request 2013-12-05 17:04:10 -08:00
wltsmrz
dc2b69d2c2 Remove a single space 2013-12-03 12:01:01 -08:00
wltsmrz
9a78d36b72 Add grunt build documentation. Fixes #31 2013-12-03 11:59:10 -08:00
wltsmrz
d14346ba15 Disable server activity check 2013-11-27 18:24:25 -08:00
wltsmrz
992b4c53b8 WebSocket isn't a Node-style EventEmitter in the browser 2013-11-27 17:29:25 -08:00
wltsmrz
385113c2f5 Remove logging 2013-11-27 15:42:53 -08:00
wltsmrz
dc04bbe737 Enhanced security for idle server reconnect 2013-11-27 15:42:05 -08:00
wltsmrz
b6a66e1820 Update readme. Fixes #15 2013-11-27 14:32:13 -08:00
wltsmrz
bcceceb2e0 In case there happens to already be a tx_json, don't overwrite it 2013-11-26 12:43:34 -08:00
wltsmrz
19c0585ff0 Remove extraneous stuff 2013-11-26 12:39:14 -08:00
wltsmrz
97df7472f5 Provisionally disable transaction broadcasting 2013-11-26 12:32:53 -08:00
wltsmrz
f05577d7df All transaction result objects will have tx_json property 2013-11-25 11:52:28 -08:00
wltsmrz
4d94aef80c Add transaction.invoiceID setter 2013-11-22 12:43:31 -08:00
wltsmrz
0902640fc0 Update transaction.jS 2013-11-21 13:19:05 -08:00
wltsmrz
bcd5e38976 Fix/refactor account_root request 2013-11-20 12:32:03 -08:00
wltsmrz
dfedc86fb7 API update 2013-11-19 14:47:57 -08:00
wltsmrz
78d110c8d1 Update transactionmanager 2013-11-19 14:39:44 -08:00
wltsmrz
4e6ae229c0 Avoid circular JSON stringification during transactionmanager trace 2013-11-18 14:52:16 -08:00
wltsmrz
49cd062cb7 Return self in set_server for chaining 2013-11-18 13:22:56 -08:00
wltsmrz
1fa196037e Fix direct requirements for tests 2013-11-18 13:08:05 -08:00
wltsmrz
674ce90eaf Don't begin to check server activity until connected 2013-11-18 13:04:11 -08:00
wltsmrz
a306407588 Cleanup 2013-11-15 23:30:18 -08:00
wltsmrz
5c81441634 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-11-15 23:15:04 -08:00
wltsmrz
cff9902802 API conversion 2013-11-15 23:13:49 -08:00
wltsmrz
08911ec2ee Refactor, begin API conversion 2013-11-15 23:13:34 -08:00
wltsmrz
79b698f232 Cleanup 2013-11-15 23:12:35 -08:00
wltsmrz
5253278c29 Normalize transactions to acount transaction stream, cleanup 2013-11-15 23:12:28 -08:00
wltsmrz
48aa5b6c01 Add server activity check / reconnect defunct servers 2013-11-15 22:38:48 -08:00
wltsmrz
7e5c4af53d Cleanup, comments, update for new style 2013-11-14 14:30:29 -08:00
wltsmrz
9b3bb77d0e Merge pull request #27 from stevenzeiler/develop
[TEST] Add chai and sinon for testing, begin testing remote.js
2013-11-13 18:35:17 -08:00
wltsmrz
a72995642c request_connect > requestConnect 2013-11-13 17:20:04 -08:00
wltsmrz
12562d68f8 Add camelcase API, remote.request accepts a string command argument 2013-11-13 17:11:37 -08:00
wltsmrz
8805610413 Create account transactionmanagers in account constructor 2013-11-13 16:12:04 -08:00
wltsmrz
b9082a425f Normalize objects emitted on transaction success 2013-11-13 11:49:18 -08:00
wltsmrz
c1f5705ef8 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-11-13 11:41:17 -08:00
wltsmrz
69758cd631 Add transactionmanager debugging 2013-11-13 11:41:13 -08:00
Stefan Thomas
05f3a97042 UInt160 can be an account or a plain hash.
The UInt160 class used to be hardcoded to be an Account. This commit changes it
so it can be used as an account or a plain hash. It will try to automatically
self-classify based on how it is initialized.

In the future we may want to have some dedicated classes rather than a
single configurable UInt160.
2013-11-12 15:51:50 -08:00
Steven Zeiler
1db2187e79 [TEST] Add chai and sinon for testing.
Begin testing remote.js
    Add initialization of 'note variable' and a comment that it should be removed
2013-11-12 14:13:15 -08:00
Stefan Thomas
ae68e3a1a6 Change "success" event parameters to more closely match "proposed" and "error". 2013-11-11 13:29:02 -08:00
Stefan Thomas
96ed994932 Fixes on XRP path serialization/deserialization. 2013-11-11 12:17:09 -08:00
Stefan Thomas
b2580ec178 Disambiguate PathSets with XRP vs XRP IOUs.
XRP IOUs are no longer valid, however they do exist in the network's historical
transactions, so the serialization and deserialization need to be able to handle
them.
2013-11-11 11:49:52 -08:00
Stefan Thomas
e065238758 Fix transaction signature cache.
The Transaction#sign method would notice that a previous signature was still
valid and skip signing again. Great, except it would throw the old
signature away first, leaving no signature at all. Fixed.
2013-11-11 05:57:40 -08:00
Stefan Thomas
34ea528ade Add serialization of XRP as an IOU if explicitly asked for.
Unfortunately, there are some transactions in the history that use this format,
so we need to support it in order to be able to verify historic ledgers.
2013-11-08 16:36:37 -08:00
Stefan Thomas
1355dc135f Fix recursive SHAMap collision handling. 2013-11-08 15:55:20 -08:00
Stefan Thomas
9652a99589 Set same criteria for currency code validity in serializedtypes.js as currency.js. 2013-11-08 15:21:04 -08:00
Stefan Thomas
8dfdced942 Fix SHAMap node collision detection. 2013-11-08 15:00:45 -08:00
wltsmrz
e779f98fb8 Export ripple-lib/Request 2013-11-08 14:40:49 -08:00
Stefan Thomas
f633beafbf Update upstream. 2013-11-08 14:38:00 -08:00
wltsmrz
81d380fb90 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-11-08 14:26:59 -08:00
wltsmrz
4be5468485 Add account_currencies 2013-11-08 14:26:42 -08:00
Stefan Thomas
a8759f1390 Fix large 32-bit integers getting deserialized as signed ints. 2013-11-08 14:21:27 -08:00
wltsmrz
5b1b41ae80 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-11-08 14:03:09 -08:00
wltsmrz
75ed909aeb Implement local transactionID generation 2013-11-08 13:58:30 -08:00
Stefan Thomas
069852a501 Fix parsing Hash256s that start with FF. 2013-11-08 12:49:13 -08:00
Stefan Thomas
5def7ba917 Add SHAMaps, metadata serialization and transaction hash calculation. 2013-11-07 22:36:06 -08:00
Stefan Thomas
74ef8f8400 Fix Int64 parsing. 2013-11-07 21:56:26 -08:00
wltsmrz
0e5431468c Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-11-07 14:05:40 -08:00
Stefan Thomas
e65f975e29 Transaction#hash. 2013-11-07 13:56:07 -08:00
wltsmrz
0f80f6d4ae Extend rippleerror properties 2013-11-07 11:47:10 -08:00
wltsmrz
3491fec469 Bump ripple-lib, mocha version 2013-11-06 01:13:02 -08:00
wltsmrz
4df88b6df5 Handle remote reconnects after max ledger wait, refactor 2013-11-06 00:41:56 -08:00
wltsmrz
2a763f8f03 Add account_currencies command 2013-11-05 17:06:51 -08:00
wltsmrz
a306d0d0e1 Do not overwrite accounts array on prepare_subscribe 2013-11-04 17:38:42 -08:00
wltsmrz
2e39f6d4a9 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-11-04 17:35:23 -08:00
wltsmrz
03930ecc9b Do not overwrite accounts array on prepare_subscribe 2013-11-04 17:30:05 -08:00
Justin Lynn
c3215e6387 add .travis.yml to silence e-mail notices 2013-11-04 17:26:50 -08:00
wltsmrz
31340c8bc1 Add Request.add_account 2013-11-04 17:26:22 -08:00
wltsmrz
fd07103e9a Request.accounts will append rather than overwrite 2013-11-04 17:00:32 -08:00
Vahe Hovhannisyan
43319267ad Add all available flags to Remote.flags. 2013-11-04 16:55:42 -08:00
Stefan Thomas
37da90e81d Typo. 2013-10-30 22:08:42 -07:00
wltsmrz
58bc1b1682 Add Request.broadcast. Subscribe to account transaction stream on all connected servers 2013-10-30 14:09:55 -07:00
wltsmrz
60e2aa613a Merge pull request #23 from tradespoke/patch-1
Update README.md
2013-10-29 23:54:35 -07:00
Filip Andersson
3f3b8dbfb9 Update README.md 2013-10-30 06:50:59 +00:00
wltsmrz
c5390100f5 Allow connection_offset of 0 2013-10-28 12:10:17 -07:00
wltsmrz
6f8d33005a Merge pull request #21 from sublimator/develop
Add hooks for logging web socket messages
2013-10-28 12:07:19 -07:00
sublimator
1c5b6b365a Add hooks for logging web socket messages
This can be used to capture web socket messages, interspersing them with metadata about the source of such messages. The rippled test suite could be captured (as per http://ndudfield.com/zencoding/rippled-capture) and it could also be useful for logging ripple-client messages for display.

The event names are pretty explicit about the intended use cases.
2013-10-25 14:42:55 +07:00
wltsmrz
8447d2c22d Bump subversion 2013-10-24 14:59:36 -07:00
wltsmrz
04b8bf277c Catch all error messages into rippleerror class 2013-10-24 12:30:27 -07:00
wltsmrz
fb3e15a079 Decrease default fee_cushion 2013-10-24 10:53:46 -07:00
wltsmrz
8959d9167f Always set build_path with local signing enabled. Fixes #20 2013-10-24 10:26:44 -07:00
wltsmrz
32d404bd44 Cleanup 2013-10-23 14:30:13 -07:00
wltsmrz
2415c668e4 Always resubmit on ter 2013-10-23 14:21:46 -07:00
wltsmrz
5c19ad2885 Pass any number of flags to set_flags 2013-10-21 16:30:46 -07:00
wltsmrz
73d41872cb Add TrustSet flags 2013-10-21 12:49:43 -07:00
wltsmrz
1eb2652edf Merge pull request #19 from moeadham/develop
[README] Correct typo
2013-10-17 20:47:20 -07:00
Moe Adham
997e561389 [README] Correct typo 2013-10-17 21:40:27 -04:00
wltsmrz
45fff5642d Resubmit after tefPAST_SEQ despite failed sequence query 2013-10-07 18:09:01 -07:00
wltsmrz
179dba81cc Bump version 2013-10-07 13:44:40 -07:00
wltsmrz
25f9d27206 More efficient to_human rounding 2013-10-06 19:19:58 -07:00
wltsmrz
fee2d3ad0e Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-10-06 03:35:46 -07:00
wltsmrz
6e54f34c4b Cache previous transaction signing hash, do not resign unless changed. Fixes #16 2013-10-06 03:34:34 -07:00
wltsmrz
7d631e5864 Round up XRP amounts. Fixes #17 2013-10-06 03:15:31 -07:00
wltsmrz
7ad0b0b093 Merge pull request #18 from mjethani/develop
Fixed issue with parsing of trade price.
2013-10-06 02:31:33 -07:00
wltsmrz
9571e7c130 More robust transactions. Load account_tx on remote reconnect 2013-10-06 02:28:11 -07:00
wltsmrz
b21d7aef0d Cleanup 2013-10-06 02:26:45 -07:00
wltsmrz
137a6a757e Cleanup 2013-10-05 15:28:30 -07:00
wltsmrz
12a5c3c701 Cleanup 2013-10-05 15:09:40 -07:00
wltsmrz
37b653eae6 Cleanup 2013-10-05 15:06:55 -07:00
wltsmrz
f24be618d8 Pause transaction processing and reload sequence on failed transactions 2013-10-05 03:23:59 -07:00
Manish Jethani
f077624212 Fixed issue with parsing of trade price.
The + operator has higher precedence, which causes the price to be read
as '' (empty string) no matter what the currency and issuer.

I believe this will fix the "Last price = n/a" error in the default
client as well.
2013-10-05 07:16:29 +05:30
wltsmrz
b4c120cea4 Fix #12 2013-10-02 21:29:52 -07:00
wltsmrz
5f69cad0cc Bump version 2013-10-02 13:29:08 -07:00
wltsmrz
76b5ca9a12 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-10-02 13:27:15 -07:00
wltsmrz
37234d2751 Fix bad signatures on resubmitted transactions 2013-10-02 13:23:02 -07:00
Vahe Hovhannisyan
abfab78f96 Docs: Add an example for request_book_offers 2013-09-28 07:56:27 -07:00
wltsmrz
53d704310a Bump version, include path to relocated jsbn for npm publishing 2013-09-26 17:56:55 -07:00
Vahe Hovhannisyan
ab1c409022 Fix stack trace bug on safari 2013-09-26 11:56:17 -07:00
wltsmrz
bf940fdec5 Do not log deprecation warnings by default 2013-09-25 18:35:36 -07:00
wltsmrz
6015ba7d25 Bump version 2013-09-24 13:04:44 -07:00
wltsmrz
72eb558f6a Bump dependencies version 2013-09-24 13:04:20 -07:00
wltsmrz
d74a7e9ea1 Update npmignore 2013-09-24 13:02:35 -07:00
Stefan Thomas
f259c7ebd7 Montgomery: Fix infinite loop for leading zero inputs. Add test. 2013-09-21 17:16:44 -07:00
Stefan Thomas
8d13065cf4 Added a benchmark for Montgomery exponentiation. 2013-09-21 16:49:34 -07:00
Stefan Thomas
5c36be59e5 More tests for Montgomery exponentiation. 2013-09-21 16:49:09 -07:00
Stefan Thomas
d2559dcdb8 Added Montgomery exponentiation for SJCL. 2013-09-21 15:23:55 -07:00
Stefan Thomas
6194d9bcd9 Left a console statement. Oops. 2013-09-21 05:50:28 -07:00
Stefan Thomas
64df821b38 JSBN and coverage tests cleanup.
Fixes compiler warning about SJCL.
Fixes coverage source highlighting issue.
Move JSBN outside of src/js/jsbn/.
2013-09-21 05:41:19 -07:00
Stefan Thomas
0986b37267 Added Jacobi functionality, more bn features - plus tests. 2013-09-19 22:24:49 -07:00
wltsmrz
26220239fa Bump version 2013-09-18 13:42:58 -07:00
wltsmrz
73fae67022 Update readme 2013-09-17 16:25:08 -07:00
wltsmrz
6dc15c6951 Update readme 2013-09-17 16:24:20 -07:00
wltsmrz
bc600a27b3 Add note about running tests 2013-09-17 16:23:03 -07:00
wltsmrz
c8e0ef01ac Bump dependencies versions 2013-09-17 15:32:07 -07:00
wltsmrz
db28f9ae46 Update npmignore 2013-09-17 15:29:27 -07:00
wltsmrz
7b4182b322 Revert "Update pathfind command arguments"
This reverts commit 0753d9cf4a.
2013-09-17 15:26:37 -07:00
wltsmrz
0753d9cf4a Update pathfind command arguments 2013-09-12 16:33:13 -07:00
wltsmrz
f337aa6a16 Use dot reporter for npm test 2013-09-12 16:24:25 -07:00
wltsmrz
7080611247 Use spec reporter for make test 2013-09-12 16:24:06 -07:00
wltsmrz
bab9fec836 Refactor currency parsing 2013-09-12 14:17:51 -07:00
wltsmrz
8dd1b62d78 Improve test coverage 2013-09-12 14:15:44 -07:00
wltsmrz
9eeb3faba3 Improve test coverage 2013-09-12 14:15:34 -07:00
wltsmrz
bba2e10e94 Test coverage 2013-09-11 14:22:47 -07:00
wltsmrz
943251b387 Refactor amount 2013-09-11 14:22:24 -07:00
wltsmrz
8b76db3942 Refactor amount 2013-09-11 13:36:06 -07:00
wltsmrz
2463fd9588 Fix human amount detection in .payment 2013-09-11 13:00:32 -07:00
wltsmrz
56a8ad3aa5 Fix base.js 2013-09-11 12:11:48 -07:00
Stefan Thomas
57aa1773cb Add the ability to generate addresses from keypairs. 2013-09-11 10:27:52 -07:00
Stefan Thomas
1f22456aed Ignore coverage related files. 2013-09-10 21:10:50 -07:00
wltsmrz
e784ad6d27 Fix grunt build with sjcl export 2013-09-10 17:46:56 -07:00
wltsmrz
068aea3ac4 Add new sjcl export for code coverage convenience 2013-09-10 17:15:52 -07:00
wltsmrz
40eccdb2fd Add new sjcl export for code coverage convenience 2013-09-10 17:13:55 -07:00
jatchili
3f2afdc39d Remove debugging statements 2013-09-10 16:49:25 -07:00
jatchili
bcaabd36be Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop
Conflicts:
	src/js/ripple/serializedtypes.js
	test/serializedtypes-test.js

JSONify complex structures before comparing them in tests.
2013-09-10 16:46:53 -07:00
jatchili
21aed214a7 messing around with serialized types test 2013-09-10 16:10:49 -07:00
wltsmrz
d761208878 Alias taker_gets and taker_pays 2013-09-10 16:00:19 -07:00
wltsmrz
2f7ce2841c transaction.payment will handle human amounts 2013-09-10 15:59:11 -07:00
wltsmrz
3fa3f6fb94 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-09-10 15:49:42 -07:00
wltsmrz
6f9c103d78 Update tests for future test coverage 2013-09-10 15:48:57 -07:00
Evan Schwartz
b131bd413c updated transaction options 2013-09-10 15:12:58 -07:00
wltsmrz
8e7d5d37b9 Remove extraneous file 2013-09-10 13:41:47 -07:00
wltsmrz
5f7a4fe042 Fix npm script running on windows 2013-09-10 12:36:28 -07:00
wltsmrz
24ef629da3 Specify parseInt base 2013-09-10 11:33:05 -07:00
wltsmrz
7e1e8a9503 Specify parseInt base 2013-09-10 11:26:41 -07:00
wltsmrz
3807c1ba6a Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-09-09 21:12:59 -07:00
wltsmrz
c3753ffc6a Allow options map for transaction commands. Closes #10 2013-09-09 21:12:45 -07:00
wltsmrz
41b66fc585 Allow options map for transaction commands. Closes #11 2013-09-09 21:11:15 -07:00
wltsmrz
95e547aff9 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-09-09 19:41:59 -07:00
wltsmrz
b6580dfe40 Cleanup 2013-09-09 15:26:21 -07:00
wltsmrz
6330777076 Cleanup 2013-09-09 14:59:19 -07:00
wltsmrz
2858579820 Remove colon 2013-09-09 14:49:14 -07:00
wltsmrz
2e7fcb0e76 Cleanup 2013-09-09 14:48:20 -07:00
wltsmrz
8cab1ae200 Cleanup 2013-09-09 14:42:07 -07:00
wltsmrz
dad760f9ba Cleanup 2013-09-09 14:28:15 -07:00
wltsmrz
e944f0f5c4 Cleanup 2013-09-09 13:58:43 -07:00
Evan Schwartz
379638da43 Merge branch 'newdocs' into develop
adding new docs to develop branch
2013-09-09 13:37:57 -07:00
Evan Schwartz
121bca2f1d about to merge back into develop branch 2013-09-09 13:37:09 -07:00
Evan Schwartz
7bf83e5c48 fixed link 2013-09-09 13:21:16 -07:00
Evan Schwartz
33c12ec93f added clickable links 2013-09-09 13:18:54 -07:00
Evan Schwartz
7a27bbb78c added listening to the network guide 2013-09-09 13:17:23 -07:00
wltsmrz
6ce83107c8 Cleanup 2013-09-09 13:05:40 -07:00
Evan Schwartz
41fb5e5b22 finished offer create guide 2013-09-09 12:06:37 -07:00
wltsmrz
8588e0c78f Fix account_lines 2013-09-09 11:59:40 -07:00
Evan Schwartz
2091d3ed29 indentation 2013-09-09 11:57:38 -07:00
Evan Schwartz
529aac8c37 added offer create guide 2013-09-09 11:57:03 -07:00
Evan Schwartz
428d31c4db added small link to guies 2013-09-09 11:07:52 -07:00
wltsmrz
aeba9ba05e Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-09-08 18:08:18 -07:00
wltsmrz
bf95285d90 Use assert.deepEqual, fix serializedtypes 2013-09-08 18:07:55 -07:00
wltsmrz
45292847ee Remove underscore devDependency 2013-09-08 18:07:30 -07:00
wltsmrz
aec3df1bf9 Remove comma 2013-09-08 17:43:19 -07:00
wltsmrz
8b1407613a Remove extraneous file 2013-09-08 17:38:02 -07:00
wltsmrz
2d68ce5f1b Update dependencies 2013-09-08 17:35:57 -07:00
wltsmrz
0a1d1f2ed4 Add Makefile 2013-09-08 17:35:50 -07:00
wltsmrz
25401bd08d Update ripple-lib tests to use mocha 2013-09-08 17:33:08 -07:00
Evan Schwartz
0ce98b126f small edit to the using remote guide 2013-09-06 17:24:38 -07:00
Evan Schwartz
db442f613e finished connecting guide 2013-09-06 17:21:16 -07:00
Evan Schwartz
b31d969d63 added connecting guide 2013-09-06 17:19:00 -07:00
Evan Schwartz
8a8159ee0b fixed bad link 2013-09-06 17:10:55 -07:00
Evan Schwartz
74c6852743 changed toc 2013-09-06 17:09:23 -07:00
wltsmrz
b9e29e8b6c Bump version 2013-09-06 17:06:49 -07:00
Evan Schwartz
9246449590 editing docs 2013-09-06 17:05:21 -07:00
Evan Schwartz
e3a6913cb0 edited readme 2013-09-06 15:47:04 -07:00
Evan Schwartz
6231d0102b changed format 2013-09-06 15:41:53 -07:00
wltsmrz
9630d92e0a Fix bin/rsign example sequence 2013-09-06 15:41:17 -07:00
Evan Schwartz
0b7ff197c8 added toc 2013-09-06 15:41:00 -07:00
Evan Schwartz
b55c547bb9 added links to readme and guides 2013-09-06 15:29:56 -07:00
Evan Schwartz
9b1c47278c fixed case 2013-09-06 15:26:59 -07:00
wltsmrz
aa6a9e9559 nextServer > next_server 2013-09-06 15:26:43 -07:00
Evan Schwartz
bddf4a8673 added links 2013-09-06 15:26:12 -07:00
wltsmrz
0aee56f4f1 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-09-06 15:23:09 -07:00
wltsmrz
fb94ef3cf3 Allow options object for complex remote commands 2013-09-06 15:22:42 -07:00
wltsmrz
7ff1e89824 Update account 2013-09-06 15:22:00 -07:00
Evan Schwartz
b5fcab50d1 added links 2013-09-06 15:21:54 -07:00
wltsmrz
69afa608bf Update transaction.js / orderbook.js 2013-09-06 15:17:42 -07:00
Evan Schwartz
9677b09ecc Merge branch 'develop' of github.com:emschwartz/ripple-lib into newdocs
just merging with remote
2013-09-06 15:16:28 -07:00
Evan Schwartz
1f3eb57fa4 added REFERENCE and GUIDES 2013-09-06 15:12:32 -07:00
wltsmrz
a501400caa Catch request errors 2013-09-06 14:27:07 -07:00
Stefan Thomas
69e44a66bb Changing to simplified version of new account_tx API. 2013-09-05 22:07:08 -07:00
wltsmrz
abec5386a6 Bump dependencies 2013-09-05 17:28:29 -07:00
jatchili
2e15bce98b Translate LedgerEntryType and TransactionResult into strings 2013-09-05 17:16:43 -07:00
wltsmrz
10ac0a5e42 Set message.result before emitting submitted event 2013-09-05 16:13:38 -07:00
wltsmrz
b723e031d1 Update rippleerror 2013-09-05 16:13:24 -07:00
wltsmrz
18c02cbb42 Add message.result = message.engine_result 2013-09-05 15:29:26 -07:00
wltsmrz
d89f04a622 Fix currency in path_rewrite 2013-09-05 15:20:17 -07:00
wltsmrz
ceea368a5c Proper max_fee comparison 2013-09-05 15:07:18 -07:00
wltsmrz
ef83019a4b Resubmit transactions on tooBusy 2013-09-05 13:17:32 -07:00
wltsmrz
28c90f1923 TransactionManager -- tec shouldn't decrement sequence 2013-09-04 17:03:19 -07:00
wltsmrz
8bc6a3cbcf Cleanup account_tx 2013-09-04 17:02:48 -07:00
wltsmrz
a46d08c6f4 Update readme 2013-09-04 17:02:30 -07:00
wltsmrz
7d63cb52cb Update readme 2013-09-04 14:40:40 -07:00
wltsmrz
81ff4eec2d Passive transaction validation -- add lost and missing events 2013-09-04 14:37:03 -07:00
wltsmrz
7a6d46acd3 Accommodate new account_tx 2013-09-04 14:34:37 -07:00
wltsmrz
700c6f0430 Update readme 2013-09-03 15:41:08 -07:00
wltsmrz
6688ad5d8d Update readme 2013-09-03 15:36:40 -07:00
wltsmrz
f3e1b6b39f Remote.ping uses new request.set_server 2013-09-03 15:29:40 -07:00
wltsmrz
d43bc13f48 request.set_server(string) selects by host 2013-09-03 15:28:32 -07:00
wltsmrz
a4ff3cbaac Update readme 2013-09-03 14:58:07 -07:00
wltsmrz
370fab5858 Update readme 2013-09-03 14:33:03 -07:00
wltsmrz
75d00f1778 Update readme 2013-09-03 14:31:03 -07:00
wltsmrz
d7ecbbf648 Update account 2013-09-03 14:13:21 -07:00
wltsmrz
bff8415ea5 Normalize RippleErrors 2013-09-03 13:57:58 -07:00
wltsmrz
0794f7b629 Update transaction queue 2013-09-03 13:52:26 -07:00
wltsmrz
b5a50aeba9 Add transaction abort 2013-09-03 13:48:59 -07:00
wltsmrz
f6cfcb6217 Rewrite rsign 2013-09-03 12:29:45 -07:00
wltsmrz
fd3a583abf Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-08-30 11:16:09 -07:00
wltsmrz
b551753fe4 Update readme 2013-08-30 11:12:47 -07:00
Vahe Hovhannisyan
b6283a554d Remote.book bug fix 2013-08-30 16:37:49 +04:00
wltsmrz
a65ba8f87d Add transaction-inbound, transaction-outbound events 2013-08-27 10:47:31 -07:00
wltsmrz
ffd23d34ab Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-08-26 10:43:48 -07:00
wltsmrz
75859a2c4d Update pending transaction fees 2013-08-26 10:43:13 -07:00
Stefan Thomas
166e35bf0e Disable currency validity check for now.
It's too strict unfortunately and not quick to fix.
2013-08-23 19:08:53 -07:00
Stefan Thomas
74da0c8a52 No need to turn the JS object into a string and back again. 2013-08-23 15:50:15 -07:00
Stefan Thomas
d5fb086b51 Digits appear in some currency codes in the ledger history.
We need to figure out what the exact allowed character set is - if there are any
restrictions.
2013-08-23 15:13:31 -07:00
Stefan Thomas
6fa2a220e7 Add some debug info to the currency parsing error. 2013-08-23 15:10:34 -07:00
wltsmrz
27fed3e737 Apply the scope of request to request callbacks 2013-08-22 12:06:13 -07:00
wltsmrz
13b62d72c6 Update readme 2013-08-22 11:53:43 -07:00
wltsmrz
4c6c967576 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-08-22 10:55:16 -07:00
wltsmrz
009143f724 Update remote 2013-08-22 10:55:09 -07:00
wltsmrz
c545e19e40 Include transaction message in inbound/outbound events 2013-08-21 17:19:17 -07:00
Stefan Thomas
87790b330e PathFind should pass on errors with the pathfind request. 2013-08-21 16:59:51 -07:00
Stefan Thomas
c9d081f2c6 Do not allow serialization of invalid accounts. 2013-08-21 16:58:49 -07:00
Stefan Thomas
222d49c4d8 Handle errors with initial subscribe request. 2013-08-21 16:58:37 -07:00
Stefan Thomas
ce8fd9ddbe Only subscribe to accounts if the Account object is set up correctly. 2013-08-21 16:58:09 -07:00
wltsmrz
ccd1758284 Add transaction-inbound and transaction-outbound events 2013-08-21 16:41:01 -07:00
Stefan Thomas
ba48078f9e Add tx_history command. 2013-08-21 11:20:12 -07:00
jatchili
f35df54eab Stringify input to BigInteger constructor 2013-08-20 18:46:11 -07:00
jatchili
f1334c6b92 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-08-20 17:36:24 -07:00
jatchili
7a5631a2ce Fix array and inner-object parsing 2013-08-20 17:36:12 -07:00
wltsmrz
9c9f8fae14 Update readme, validate all transaction addresses 2013-08-20 11:54:35 -07:00
wltsmrz
dc51b8ba48 Update readme 2013-08-17 16:25:08 -07:00
wltsmrz
e3edd5267d Fix secure configuration; Boolean constructor should be string 2013-08-16 13:15:16 -07:00
wltsmrz
8b69dfd154 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-08-15 11:53:47 -07:00
wltsmrz
8333c3fbfc Fix for typeof NaN === number 2013-08-15 11:53:38 -07:00
jatchili
083377651f Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-08-14 18:33:28 -07:00
jatchili
0c18f6157d SerializedObject to_json method, and a simple test for that. Tests are still preliminary, and refactoring is still necessary. 2013-08-14 18:33:01 -07:00
wltsmrz
66d71efa78 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-08-14 17:24:14 -07:00
wltsmrz
0811dec65c Update server 2013-08-14 17:21:08 -07:00
wltsmrz
b3822cc4c5 Update everything 2013-08-14 17:20:51 -07:00
Stefan Thomas
1305acb849 Bump version to 0.7.19. 2013-08-14 17:15:14 -07:00
Stefan Thomas
1e1e70d9a6 Add Amount#invert. 2013-08-14 17:11:30 -07:00
jatchili
942bfe88c1 passed tests (however, the test cases themselves are unverified.) 2013-08-12 18:44:34 -07:00
jatchili
63038d3603 basic tests for serializedtypes.js 2013-08-12 15:19:04 -07:00
wltsmrz
0f5fdbc772 Merge branch 'develop' of https://github.com/ripple/ripple-lib into robust-transactions 2013-08-12 10:40:07 -07:00
Stefan Thomas
8640176a07 Move debug statement in pathfind.js to proper place. 2013-08-09 17:10:25 -07:00
Stefan Thomas
e49838437e Add Account#lines. 2013-08-08 18:43:27 -07:00
Stefan Thomas
7c5e9bae2a Use the initial path find response as a first set of alternatives. 2013-08-08 18:43:27 -07:00
Vahe Hovhannisyan
08a2d771cd WebSocket support for some old browsers 2013-08-08 11:26:20 +02:00
wltsmrz
36f8ca1073 Merge branch 'robust-transactions' of https://github.com/ripple/ripple-lib into robust-transactions 2013-08-07 17:58:28 -07:00
wltsmrz
1e288c31af Merge pull request #9 from sublimator/robust-transactions
Allow hooking of the Server WebSocket constructor object
2013-08-07 17:47:27 -07:00
Nicholas Dudfield
e8144128d0 Naming style, oopz 2013-08-07 17:20:23 -07:00
Nicholas Dudfield
e5421effd5 Allow hooking of the Server WebSocket constructor object 2013-08-07 17:11:29 -07:00
wltsmrz
48a2bb9fe4 Update readme 2013-08-07 08:37:07 +09:00
wltsmrz
67d100796d Cleanup 2013-08-07 08:16:20 +09:00
wltsmrz
0f91bf0e98 Add custom RippleError 2013-08-07 07:35:37 +09:00
wltsmrz
1aaee7526b Cleanup 2013-08-07 06:26:43 +09:00
wltsmrz
fd0ce52cf6 Cleanup 2013-08-06 06:54:06 +09:00
wltsmrz
0fd973f58b Add ledger_hash paramter to transaction_entry 2013-08-06 06:53:41 +09:00
wltsmrz
a99856bec0 Cleanup 2013-08-06 06:17:40 +09:00
wltsmrz
eac195ec18 Cleanup 2013-08-06 04:20:05 +09:00
wltsmrz
ab5d64ff12 Update repository URL 2013-08-03 09:01:55 +09:00
wltsmrz
5f017b2b85 Cleanup, fix jshint warnings 2013-08-03 08:13:36 +09:00
wltsmrz
bb6ec3b5c9 Replace comment 2013-08-03 07:27:59 +09:00
wltsmrz
d870264230 Update untracked files 2013-08-03 07:23:12 +09:00
wltsmrz
aad97c936d Update untracked files 2013-08-03 07:19:15 +09:00
wltsmrz
b78835f195 Typo 2013-08-03 07:18:02 +09:00
wltsmrz
a480e76353 Update Remote constructor comments 2013-08-02 08:27:28 +09:00
wltsmrz
8294c78db4 Greater convenience for payment transactions 2013-08-02 08:17:14 +09:00
Stefan Thomas
c06c74e460 Basic implementation for new path finding API. 2013-07-31 19:54:07 -07:00
jatchili
3ea426d45f Path Set parsing function (not tested yet) 2013-07-31 19:18:42 -07:00
wltsmrz
bc74543ca2 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-08-01 06:04:59 +09:00
wltsmrz
c69966035b Cleanup transaction submission 2013-08-01 06:04:49 +09:00
Stefan Thomas
78b1e4f570 More work on new serialization/parsing functions. Added more tests. 2013-07-30 22:48:07 -07:00
Stefan Thomas
815e2af3cf Fix code indentation. 2013-07-30 21:32:28 -07:00
jatchili
5bc1603f43 begin testing serialization/parsing functions 2013-07-30 20:02:30 -07:00
Stefan Thomas
c48bf4089c Fix serialization of XRP in PathSets. 2013-07-29 18:28:37 -07:00
Stefan Thomas
fd67ea3036 Refactor: Currency parsing. 2013-07-29 18:28:25 -07:00
jatchili
006d1fadce Don't prepend length when you're serializing a Hash256 2013-07-29 17:49:46 -07:00
Stefan Thomas
a1206f6e3f Expose some of the modules related to the binary format. 2013-07-29 13:09:19 -07:00
Stefan Thomas
be2d647d48 Testutils is not used in this testsuite. 2013-07-27 19:46:32 -07:00
Stefan Thomas
9d863c4dfa Bump version to 0.7.18. 2013-07-27 19:36:27 -07:00
wltsmrz
a1bef6248a Accommodate new transaction manager 2013-07-27 05:57:30 +09:00
Stefan Thomas
6bb9f9e5e4 Implement Remote#reserve(). 2013-07-26 13:02:26 -07:00
Stefan Thomas
f69ad8b172 Remote#fee_tx: Return an Amount of fees for a specified number of units. 2013-07-26 13:02:21 -07:00
wltsmrz
2f4a9c2c26 Merge branch 'develop' of https://github.com/ripple/ripple-lib into develop 2013-07-27 04:03:36 +09:00
wltsmrz
342d5a1aa1 Update remote 2013-07-27 04:03:33 +09:00
Stefan Thomas
6e2170156d Calling buster-test instead of buster test seems to be safer. 2013-07-26 00:05:45 -07:00
Stefan Thomas
a8a760fc41 Merge commit '8e54231a6c664154302dd72c4ce935233ae8d8de' as 'src/js/sjcl' 2013-07-25 23:25:59 -07:00
Stefan Thomas
8e54231a6c Squashed 'src/js/sjcl/' content from commit 28d8573
git-subtree-dir: src/js/sjcl
git-subtree-split: 28d8573235787113fd60daf9d786418695797824
2013-07-25 23:25:59 -07:00
Stefan Thomas
78fdd6bae7 Remove SJCL submodule. 2013-07-25 23:13:34 -07:00
Stefan Thomas
89a8e6c362 Add easy way to run local grunt-cli: npm run-script build 2013-07-25 22:20:08 -07:00
Stefan Thomas
03a6583032 Set default fee values, helps rippled unit tests. 2013-07-25 22:13:13 -07:00
wltsmrz
a5206d2959 Safer response check, add _remote_address(), attach server to request 2013-07-26 07:25:10 +09:00
wltsmrz
4e876511a3 Connect to servers on an interval 2013-07-26 06:52:20 +09:00
Stefan Thomas
09e190c822 Merge branch 'develop' 2013-07-23 21:36:43 -07:00
Stefan Thomas
d08e06ad53 Bump version to 0.7.17. 2013-07-23 21:36:31 -07:00
Stefan Thomas
1f40bb7ea8 Increase default fee cushion. 2013-07-23 21:30:18 -07:00
Stefan Thomas
3e1e62c03a Dirty fix: Don't request transaction_entry on finalized transactions. 2013-07-23 21:26:02 -07:00
Stefan Thomas
6bc2493d8d Add fee calculation based on load. 2013-07-23 21:03:13 -07:00
Stefan Thomas
0d00a30d19 Add ability to limit number of significant digits in Amount#to_human. 2013-07-23 19:50:09 -07:00
wltsmrz
c70e8dcf01 Do not emit error on timeout, allow timeout callback 2013-07-19 04:06:45 +09:00
wltsmrz
06c3a87929 Add npmignore 2013-07-18 07:58:55 +09:00
wltsmrz
5b11795dc3 Bump devDependencies 2013-07-18 07:37:06 +09:00
wltsmrz
9a6d4adfbe Update readme 2013-07-18 07:26:34 +09:00
wltsmrz
913df130e8 Update readme 2013-07-18 07:24:23 +09:00
wltsmrz
1222599c6c Update readme 2013-07-18 07:23:39 +09:00
wltsmrz
b51d677145 Add request.timeout 2013-07-18 05:29:27 +09:00
wltsmrz
4eb516e072 Add .connect callback 2013-07-18 03:48:27 +09:00
wltsmrz
451e6f9b86 Update readme 2013-07-17 04:44:34 +09:00
wltsmrz
151a4fa1c0 Bump ripple-lib and dependencies versions 2013-07-13 05:46:33 +09:00
wltsmrz
a025c187cb Update readme 2013-07-13 05:39:56 +09:00
wltsmrz
9550edab9b Fixes and cleanup for Remote, update readme 2013-07-13 05:31:40 +09:00
wltsmrz
9ec72ee8c5 Default maxListeners to infinity 2013-07-11 07:00:33 +09:00
wltsmrz
d93145fc1e Cleanup 2013-07-10 06:16:49 +09:00
wltsmrz
087f357714 Callbacks 2013-07-10 05:59:00 +09:00
Stefan Thomas
0fcce464bb Fix invalid parentheses. 2013-07-01 16:18:34 +01:00
Stefan Thomas
44bb8658f0 Implement serializing Hash256. 2013-07-01 16:11:50 +01:00
Stefan Thomas
be6bd8cc5d Fix typo. 2013-07-01 16:11:36 +01:00
wltsmrz
3812ac7d53 Cleanup 2013-06-26 04:48:29 +09:00
wltsmrz
6e36ded34f Multi-server API 2013-06-26 04:22:12 +09:00
wltsmrz
07d33d817b Fix .disconnect 2013-06-26 04:09:30 +09:00
wltsmrz
28f7b483a8 Fix 2013-06-26 03:47:58 +09:00
wltsmrz
722ff4dc5c Revert "Enable multi-server fallback"
This reverts commit 5a1b9d1f90.
2013-06-26 03:42:46 +09:00
wltsmrz
13b7c9bfbb Revert "Wrap JSON.parse in try/catch"
This reverts commit 6704120105.
2013-06-26 03:41:46 +09:00
wltsmrz
daaac98bef Revert "Add field to server configuration for concurrent connections to the same host"
This reverts commit 348615671a.
2013-06-26 03:41:33 +09:00
wltsmrz
84e79abbaa Revert "Cleanup, fix .disconnect"
This reverts commit c2ad23996d.
2013-06-26 03:40:35 +09:00
wltsmrz
2a340d09a4 Typo 2013-06-26 03:40:11 +09:00
wltsmrz
db5c3f1bf2 Revert "Add JSDoc style comments"
This reverts commit 75d3a4f827.
2013-06-26 03:37:10 +09:00
wltsmrz
705a9ad087 Fix 2013-06-26 03:36:58 +09:00
Stefan Thomas
fa1a4a0307 Undo change to the way ripple-lib unsubscribed from ws events. 2013-06-25 20:34:10 +02:00
wltsmrz
ce1338cf20 Revert "Support callback style"
This reverts commit c2b03e9d93.

Conflicts:
	src/js/ripple/remote.js
2013-06-26 03:29:49 +09:00
wltsmrz
28408bfa24 Revert "Add offline queue, update code comments"
This reverts commit 9dd337a16c.
2013-06-26 03:28:50 +09:00
Stefan Thomas
dc290f69ec Move require('ws') back to where it was.
Add comment explaining why it's there.
2013-06-25 17:41:37 +02:00
Stefan Thomas
dde93f5e8d Fix typo. 2013-06-24 17:18:25 +02:00
Stefan Thomas
d862d17770 Fix typo. 2013-06-24 15:08:29 +02:00
Stefan Thomas
58ca70ee1c Merge branch 'develop' 2013-06-24 13:48:41 +02:00
Stefan Thomas
ed1bc7fb25 Bump version to 0.7.15. 2013-06-24 13:48:28 +02:00
Stefan Thomas
7cb9787424 Expose Base58 functionality in API. 2013-06-24 13:47:55 +02:00
Arthur Britto
be78db1563 Add optional cancel support to offer_create. 2013-06-15 22:28:12 -07:00
wltsmrz
9dd337a16c Add offline queue, update code comments 2013-06-13 13:39:55 +09:00
wltsmrz
c2b03e9d93 Support callback style 2013-06-13 08:32:13 +09:00
wltsmrz
75d3a4f827 Add JSDoc style comments 2013-06-13 07:28:36 +09:00
wltsmrz
c2ad23996d Cleanup, fix .disconnect 2013-06-13 07:17:01 +09:00
wltsmrz
348615671a Add field to server configuration for concurrent connections to the same host 2013-06-13 04:06:15 +09:00
wltsmrz
6704120105 Wrap JSON.parse in try/catch 2013-06-12 04:03:00 +09:00
wltsmrz
45a3ac3f66 Fallback to config-example for tests 2013-06-11 08:27:31 +09:00
wltsmrz
5a1b9d1f90 Enable multi-server fallback 2013-06-11 04:00:00 +09:00
wltsmrz
355a2b076b Set _connected 2013-06-11 03:38:31 +09:00
wltsmrz
82224de494 Add engines field 2013-06-09 09:38:13 +09:00
wltsmrz
8a1c5a7b66 Bump dependencies versions 2013-06-09 09:37:26 +09:00
wltsmrz
8feaae0e2d Cleanup 2013-06-08 13:48:20 +09:00
Stefan Thomas
a0e1c07425 Merge branch 'develop' 2013-06-06 12:43:54 +02:00
Stefan Thomas
514dff0e2c Bump version to 0.7.14 2013-06-06 12:43:36 +02:00
Stefan Thomas
10c883e432 Security: Enable local_signing by default. 2013-06-03 13:15:41 +02:00
Arthur Britto
b0c8d67d27 Fixed adding negative to zero. Fixes #6. 2013-05-30 23:59:58 -07:00
Arthur Britto
6601edadb2 Fixed adding negative to zero. Fixes #6. 2013-05-30 21:58:58 -07:00
Arthur Britto
9de7b285f0 Fix reporting of offline status to new states. 2013-05-23 18:23:33 -07:00
ahbritto
c8471e5d11 Merge pull request #4 from abrkn/master
fix repo url in package.json
2013-05-23 16:50:25 -07:00
Arthur Britto
8ff5f6022a Merge branch 'vinnie' of github.com:vinniefalco/ripple-lib into vinniefalco-vinnie 2013-05-23 16:44:13 -07:00
Arthur Britto
3a4cd93629 Version 0.7.13 2013-05-23 15:34:18 -07:00
Arthur Britto
ba2f290100 Add support for AccountSet and account_root flags. 2013-05-23 15:33:33 -07:00
Arthur Britto
337bb03a00 Fix rsign.js require path. 2013-05-23 15:32:58 -07:00
Arthur Britto
b651bc80f9 Merge branch 'addcpperrs' 2013-05-11 23:31:28 -07:00
Arthur Britto
b3c96419d4 Merge branch 'addcpperrs' into develop 2013-05-11 23:31:17 -07:00
Arthur Britto
a813b0cc9d Add TransactionErr files. 2013-05-11 23:30:00 -07:00
Stefan Thomas
a22de5e84a Fix library build in grunt. 2013-05-07 20:54:04 +02:00
Stefan Thomas
44d1f1457b Add correct handling of NaN in product_human and ratio_human. 2013-05-07 17:00:43 +02:00
Stefan Thomas
22a954deed Changed directory structure to be more like server's. src/js/*.js -> src/js/ripple/ 2013-05-06 21:32:25 +02:00
Stefan Thomas
bad52779e3 Upgrade grunt to latest.
Also adds (experimental) dox documentation generator. Run with `grunt dox`. Documentation will be placed under build/docs/.
2013-05-06 21:10:53 +02:00
Stefan Thomas
8073fc7979 Remove CryptoJS, no longer used. 2013-05-06 21:08:35 +02:00
Andreas Brekken
c7a464b0e2 fix repo url in package.json 2013-05-03 09:25:15 +02:00
Vinnie Falco
34d8a77bc0 Add some ctor docs 2013-05-01 16:17:09 -07:00
Vinnie Falco
498e7df622 Add maxListeners config option 2013-05-01 16:16:50 -07:00
Arthur Britto
60587b59ee Version 0.7.12 2013-04-27 12:40:41 -07:00
Arthur Britto
d1c1ac7fbb Adjust rsign.js for being in ripple-lib. 2013-04-27 12:35:45 -07:00
Stefan Thomas
0622cc9766 Add bin/rsign.js. 2013-04-27 15:15:20 +02:00
Arthur Britto
20032e073f Merge branch 'master' into develop 2013-04-26 16:08:30 -07:00
285 changed files with 54615 additions and 5206 deletions

7
.gitignore vendored
View File

@@ -17,7 +17,7 @@
# Ignore object files.
*.o
build
build/ripple*.js
tags
bin/rippled
Debug/*.*
@@ -37,3 +37,8 @@ db/*.db-*
rippled.cfg
validators.txt
test/config.js
# Ignore coverage files
/lib-cov
/src-cov
/coverage.html

10
.gitmodules vendored
View File

@@ -1,10 +0,0 @@
[submodule "src/js/cryptojs"]
path = src/js/cryptojs
url = git://github.com/gwjjeff/cryptojs.git
[submodule "src/js/sjcl"]
path = src/js/sjcl
url = git://github.com/bitwiseshiftleft/sjcl.git
ignore = dirty
[submodule "src/cpp/leveldb"]
path = src/cpp/leveldb
url = https://code.google.com/p/leveldb/

3
.npmignore Normal file
View File

@@ -0,0 +1,3 @@
deploy
lib-cov
coverage.html

6
.travis.yml Normal file
View File

@@ -0,0 +1,6 @@
language: node_js
node_js:
- "0.10"
notifications:
email:
false

View File

@@ -1,8 +1,11 @@
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-webpack');
grunt.loadNpmTasks('grunt-dox');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.initConfig({
pkg: '<json:package.json>',
pkg: grunt.file.readJSON('package.json'),
meta: {
banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %>\n' +
@@ -36,46 +39,65 @@ module.exports = function(grunt) {
"src/js/sjcl-custom/sjcl-secp256k1.js",
"src/js/sjcl-custom/sjcl-ripemd160.js",
"src/js/sjcl-custom/sjcl-extramath.js",
"src/js/sjcl-custom/sjcl-montgomery.js",
"src/js/sjcl-custom/sjcl-validecc.js",
"src/js/sjcl-custom/sjcl-ecdsa-der.js"
"src/js/sjcl-custom/sjcl-ecdsa-der.js",
"src/js/sjcl-custom/sjcl-jacobi.js"
],
dest: 'build/sjcl.js'
}
},
webpack: {
options: {
entry: "./src/js/ripple/index.js",
output: {
library: "ripple"
},
cache: true
},
lib: {
src: "src/js/index.js",
dest: "build/ripple-<%= pkg.version %>.js",
libary: "ripple", // misspelling fixed in later versions of webpack
library: "ripple"
output: {
filename: "build/ripple-<%= pkg.version %>.js"
}
},
lib_debug: {
src: "src/js/index.js",
dest: "build/ripple-<%= pkg.version %>-debug.js",
libary: "ripple", // misspelling fixed in later versions of webpack
library: "ripple",
debug: true
output: {
filename: "build/ripple-<%= pkg.version %>-debug.js"
},
debug: true,
devtool: 'eval'
},
lib_min: {
src: "src/js/index.js",
dest: "build/ripple-<%= pkg.version %>-min.js",
libary: "ripple", // misspelling fixed in later versions of webpack
library: "ripple",
minimize: true
output: {
filename: "build/ripple-<%= pkg.version %>-min.js"
},
optimize: {
minimize: true
}
}
},
watch: {
sjcl: {
files: ['<config:concat.sjcl.src>'],
files: ['<%= concat.sjcl.src %>'],
tasks: 'concat:sjcl'
},
lib: {
files: 'src/js/*.js',
tasks: 'webpack'
files: 'src/js/ripple/*.js',
tasks: 'webpack:lib_debug'
}
},
dox: {
libdocs: {
options: {
title: "Test"
},
src: ['src/js/ripple/'],
dest: 'build/docs'
}
}
});
// Tasks
grunt.registerTask('default', 'concat:sjcl webpack');
grunt.registerTask('default', ['concat:sjcl', 'webpack']);
};

45
HISTORY.md Normal file
View File

@@ -0,0 +1,45 @@
##0.7.35
+ `LastLedgerSequence` is set by default on outgoing transactions. This refers to the last valid ledger index (AKA sequence) for a transaction. By default, this index is set to the current index (at submission time) plus 8. In theory, this allows ripple-lib to deterministically fail a transaction whose submission request timed out, but whose associated server continues to emit ledger_closed events.
+ Transactions that err with `telINSUF_FEE_P` will be automatically resubmitted. This error indicates that the `Fee` supplied in the transaction submission request was inadquate. Ideally, the `Fee` is tracked by ripple-lib in real-time, and the resubmitted transaction will most likely succeed.
+ Added Transaction.iff(function(callback) { }). Callback expects first argument to be an Error or null, second argument is a boolean which indicates whether or not to proceed with the transaction submission. If an `iff` function is specified, it will be executed prior to every submission of the transaction (including resubmissions).
+ Transactions will now emit `presubmit` and `postsubmit` events. They will be emitted before and after a transaction is submitted, respectively.
+ Added Transaction.summary(). Returns a summary of a transaction in semi-human-readable form. JSON-stringifiable.
+ Remote.requestAccountTx() with `binary: true` will automatically parse transactions.
+ Added Remote.requestAccountTx filter, map, and reduce.
```js
remote.requestAccountTx({
account: 'retc',
ledger_index_min: -1,
ledger_index_max: -1,
limit: 100,
binary: true,
filter: function(transaction) {
return transaction.tx.TransactionType === 'Payment';
},
map: function(transaction) {
return Number(transaction.tx.Amount);
},
reduce: function(a, b) {
return a + b;
},
pluck: 'transactions'
}, console.log)
```
+ Added persistence hooks.
+ General performance improvements, especially for long-running processes.

62
LICENSE
View File

@@ -1,4 +1,4 @@
Copyright (c) 2012,2013 OpenCoin, Inc.
Copyright (c) 2012,2013,2014 Ripple Labs Inc.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -12,39 +12,39 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--------------------------------------
Some code from Tom Wu:
This software is covered under the following copyright:
/*
* Copyright (c) 2003-2005 Tom Wu
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
*
* IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
* THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* In addition, the following condition applies:
*
* All redistributions must retain an intact copy of this copyright notice
* and disclaimer.
*/
Copyright (c) 2003-2005 Tom Wu
All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
In addition, the following condition applies:
All redistributions must retain an intact copy of this copyright notice
and disclaimer.
Address all questions regarding this license to:

12
Makefile Normal file
View File

@@ -0,0 +1,12 @@
test:
mocha --reporter spec test/*-test.js
coverage:
rm -rf src-cov
mkdir src-cov
mkdir src-cov/js
jscoverage --no-highlight src/js/ripple src-cov/js/ripple
RIPPLE_LIB_COV=1 mocha --reporter html-cov test/*-test.js > coverage.html
rm -rf src-cov
.PHONY: test

View File

@@ -1,11 +1,93 @@
Ripple JavaScript Library - ripple-lib
======================================
#The Ripple JavaScript Library
This library can connect to the Ripple network via the WebSocket protocol and runs in Node.js as well as in the browser.
`ripple-lib` connects to the Ripple network via the WebSocket protocol and runs in Node.js as well as in the browser.
Build instructions:
* https://ripple.com/wiki/Ripple_JavaScript_library
**Use ripple-lib for**
For more information:
* https://ripple.com
* https://ripple.com/wiki
+ Connecting to a local or remote rippled in JavaScript (Node.js or browser)
+ 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. Overview
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. [The `ripple-lib` Guides (docs/GUIDES.md)](docs/GUIDES.md)
2. [The `ripple-lib` API Reference (docs/REFERENCE.md)](docs/REFERENCE.md)
3. https://ripple.com/wiki/Ripple_JavaScript_library
###Also see:
+ https://ripple.com/wiki
+ https://ripple.com
##Getting `ripple-lib`
**Via npm for Node.js**
```
$ npm install ripple-lib
```
**Build from the source using `grunt`**
```
$ git clone https://github.com/ripple/ripple-lib
$ npm install
$ grunt
```
Then use the minified `build/ripple-*-min.js` in your webpage
##Quickstart
`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:
```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({
// see the API Reference for available options
trusted: true,
local_signing: true,
local_fee: true,
fee_cushion: 1.5,
servers: [
{
host: 's1.ripple.com'
, port: 443
, secure: true
}
]
});
remote.connect(function() {
/* remote connected */
// see the API Reference for available functions
});
```
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
1. Clone the repository
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`
**Generating code coverage**
ripple-lib uses `jscoverage` to generate code coverage. To generate a file `coverage.html`, run `make coverage`

44
bench/modpow.js Normal file
View File

@@ -0,0 +1,44 @@
var Benchmark;
try {
Benchmark = require('benchmark');
} catch (e) {
console.error("Please install Benchmark.js: npm install benchmark");
process.exit(1);
}
var sjcl = require('../build/sjcl');
var jsbn = require('../src/js/jsbn/jsbn');
var base = "3f70f29d3f3ae354a6d2536ceafba83cfc787cd91e7acd2b6bde05e62beb8295ae18e3f786726f8d034bbc15bf8331df959f59d431736d5f306aaba63dacec279484e39d76db9b527738072af15730e8b9956a64e8e4dbe868f77d1414a8a8b8bf65380a1f008d39c5fabe1a9f8343929342ab7b4f635bdc52532d764701ff3d8072c475c012ff0c59373e8bc423928d99f58c3a6d9f6ab21ee20bc8e8818fc147db09f60c81906f2c6f73dc69725f075853a89f0cd02a30a8dd86b660ccdeffc292f398efb54088c822774445a6afde471f7dd327ef9996296898a5747726ccaeeceeb2e459df98b4128cb5ab8c7cd20c563f960a1aa770f3c81f13f967b6cc";
var exponent = "322e393f76a1c22b147e7d193c00c023afb7c1500b006ff1bc1cc8d391fc38bd";
var modulus = "c7f1bc1dfb1be82d244aef01228c1409c198894eca9e21430f1669b4aa3864c9f37f3d51b2b4ba1ab9e80f59d267fda1521e88b05117993175e004543c6e3611242f24432ce8efa3b81f0ff660b4f91c5d52f2511a6f38181a7bf9abeef72db056508bbb4eeb5f65f161dd2d5b439655d2ae7081fcc62fdcb281520911d96700c85cdaf12e7d1f15b55ade867240722425198d4ce39019550c4c8a921fc231d3e94297688c2d77cd68ee8fdeda38b7f9a274701fef23b4eaa6c1a9c15b2d77f37634930386fc20ec291be95aed9956801e1c76601b09c413ad915ff03bfdc0b6b233686ae59e8caf11750b509ab4e57ee09202239baee3d6e392d1640185e1cd";
var expected = "5b3823974b3eda87286d3f38499de290bd575d8b02f06720acacf3d50950f9ca0ff6b749f3be03913ddca0b291e0b263bdab6c9cb97e4ab47ee9c235ff20931a8ca358726fab93614e2c549594f5c50b1c979b34f840b6d4fc51d6feb2dd072995421d17862cb405e040fc1ed662a3245a1f97bbafa6d1f7f76c7db6a802e3037acdf01ab5053f5da518d6753477193b9c25e1720519dcb9e2f6e70d5786656d356151845a49861dfc40187eff0e85cd18b1f3f3b97c476472edfa090b868b2388edfffecc521c20df8cebb8aacfb3669b020330dd6ea64b2a3067a972b8f249bccc19347eff43893e916f0949bd5789a5cce0f8b7cd87cece909d679345c0d4";
var BigInteger = jsbn.BigInteger;
var jsbnBase = new BigInteger(base, 16);
var jsbnExponent = new BigInteger(exponent, 16);
var jsbnModulus = new BigInteger(modulus, 16);
var bn = sjcl.bn;
var sjclBase = new bn(base);
var sjclExponent = new bn(exponent);
var sjclModulus = new bn(modulus);
var suite = new Benchmark.Suite;
// add tests
suite.add('jsbn#modPow', function() {
jsbnBase.modPow(jsbnExponent, jsbnModulus);
});
suite.add('sjcl#powermodMontgomery', function() {
sjclBase.powermodMontgomery(sjclExponent, sjclModulus);
});
suite.on('cycle', function(event) {
console.log(String(event.target));
});
suite.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
});
// run async
console.log("Running benchmark...");
suite.run({ 'async': false });

47
bin/decode_binary.js Executable file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env node
var SerializedObject = require('../src/js/ripple/serializedobject').SerializedObject;
var argv = process.argv.slice(2);
var blob;
blob = argv.shift();
if (blob === '-') {
read_input(ready);
} else {
ready();
}
function read_input(callback) {
tx_json = '';
process.stdin.on('data', function(data) { tx_json += data; });
process.stdin.on('end', callback);
process.stdin.resume();
}
function ready() {
var valid_arguments = blob;
if (!valid_arguments) {
console.error('Invalid arguments\n');
print_usage();
} else {
decode();
}
}
function print_usage() {
console.log(
'Usage: decode_binary.js <hex_blob>\n\n',
'Example: decode_binary.js 120000240000000161D6871AFD498D00000000000000000000000000005553440000000000550FC62003E785DC231A1058A05E56E3F09CF4E668400000000000000A732102AE75B908F0A95F740A7BFA96057637E5C2170BC8DAD13B2F7B52AE75FAEBEFCF811450F97A072F1C4357F1AD84566A609479D927C9428314550FC62003E785DC231A1058A05E56E3F09CF4E6'
);
};
function decode() {
buffer = new SerializedObject(blob);
console.log(buffer.to_json());
};
// vim:sw=2:sts=2:ts=8:et

96
bin/rsign.js Executable file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env node
var Transaction = require('../src/js/ripple/transaction').Transaction;
var argv = process.argv.slice(2);
var verbose;
var secret;
var tx_json;
if (~argv.indexOf('-v')){
argv.splice(argv.indexOf('-v'), 1);
verbose = true;
}
secret = argv.shift();
tx_json = argv.shift();
if (tx_json === '-') {
read_input(ready);
} else {
ready();
}
function read_input(callback) {
tx_json = '';
process.stdin.on('data', function(data) { tx_json += data; });
process.stdin.on('end', callback);
process.stdin.resume();
}
function ready() {
var valid_arguments = secret && tx_json;
if (!valid_arguments) {
console.error('Invalid arguments\n');
print_usage();
} else {
var valid_json = true;
try {
tx_json = JSON.parse(tx_json);
} catch(exception) {
valid_json = false;
}
if (!valid_json) {
console.error('Invalid JSON\n');
print_usage();
} else {
sign_transaction();
}
}
}
function print_usage() {
console.log(
'Usage: rsign.js <secret> <json>\n\n',
'Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC',
JSON.stringify({
TransactionType: 'Payment',
Account: 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi',
Destination: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV',
Amount: '200000000',
Fee: '10',
Sequence: 1
})
);
};
function sign_transaction() {
var tx = new Transaction();
tx.tx_json = tx_json;
tx._secret = secret;
tx.complete();
var unsigned_blob = tx.serialize().to_hex();
var unsigned_hash = tx.signingHash();
tx.sign();
if (verbose) {
var sim = { };
sim.tx_blob = tx.serialize().to_hex();
sim.tx_json = tx.tx_json;
sim.tx_signing_hash = unsigned_hash;
sim.tx_unsigned = unsigned_blob;
console.log(JSON.stringify(sim, null, 2));
} else {
console.log(tx.serialize().to_hex());
}
};
// vim:sw=2:sts=2:ts=8:et

26
bin/validate_address.js Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env node
var UInt160 = require('../').UInt160;
var address = process.argv[2];
if (address === '-') {
readInput(validateAddress);
} else {
validateAddress(address);
}
function readInput(callback) {
var result = '';
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(data) {
result += data;
});
process.stdin.on('end', function() {
callback(result);
});
};
function validateAddress(address) {
process.stdout.write((UInt160.is_valid(address.trim()) ? '0' : '1') + '\r\n');
};

4125
build/sjcl.js Normal file

File diff suppressed because it is too large Load Diff

201
docs/GUIDES.md Normal file
View File

@@ -0,0 +1,201 @@
#`ripple-lib` Guides
This file provides step-by-step walkthroughs for some of the most common usages of `ripple-lib`.
###Guides in this document:
1. [Connecting to the Ripple network with `Remote`](GUIDES.md#1-connecting-to-the-ripple-network-with-remote)
2. [Using `Remote` functions and `Request` objects](GUIDES.md#2-using-remote-functions-and-request-objects)
3. [Submitting a payment to the network](GUIDES.md#3-submitting-a-payment-to-the-network)
* [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. [Listening to the network](GUIDES.md#5-listening-to-the-network)
###Also see:
1. [The `ripple-lib` README](../README.md)
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:
```js
/* Loading ripple-lib with Node.js */
var Remote = require('ripple-lib').Remote;
/* Loading ripple-lib in a webpage */
// var Remote = ripple.Remote;
```
3. Create a new `Remote` and connect to the network:
```js
var remote = new Remote({options});
remote.connect(function() {
/* remote connected, use some remote functions here */
});
```
__NOTE:__ See the API Reference for available [`Remote` options](REFERENCE.md#1-remote-options)
4. You're connected! Read on to see what to do now.
##2. Using `Remote` functions and `Request` objects
All `Remote` functions return a `Request` object.
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.
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):
+ Using a `Remote` function with `Request` event listeners:
```js
var request = remote.request_server_info();
request.on('success', function(res) {
//handle success
});
request.on('error', function(err) {
//handle error
});
request.request(); // this triggers the request if it has not already been sent to the server
```
+ Using a `Remote` function with a callback:
```js
remote.request_server_info(function(err, res) {
if (err) {
//handle error
} else {
//handle success
}
});
```
__NOTE:__ See the API Reference for available [`Remote` functions](REFERENCE.md#2-remote-functions)
##3. Submitting a payment to the network
Submitting a payment transaction to the Ripple network involves connecting to a `Remote`, creating a transaction, signing it with the user's secret, and submitting it to the `rippled` server. Note that the `Amount` module is used to convert human-readable amounts like '1XRP' or '10.50USD' to the type of Amount object used by the Ripple network.
```js
/* Loading ripple-lib Remote and Amount modules in Node.js */
var Remote = require('ripple-lib').Remote;
var Amount = require('ripple-lib').Amount;
/* Loading ripple-lib Remote and Amount modules in a webpage */
// var Remote = ripple.Remote;
// var Amount = ripple.Amount;
var MY_ADDRESS = 'rrrMyAddress';
var MY_SECRET = 'secret';
var RECIPIENT = 'rrrRecipient';
var AMOUNT = Amount.from_human('1XRP');
var remote = new Remote({ /* Remote options */ });
remote.connect(function() {
remote.set_secret(MY_ADDRESS, MY_SECRET);
var transaction = remote.transaction();
transaction.payment({
from: MY_ADDRESS,
to: RECIPIENT,
amount: AMOUNT
});
transaction.submit(function(err, res) {
/* handle submission errors / success */
});
});
```
###A note on transaction fees
A full description of network transaction fees can be found on the [Ripple Wiki](https://ripple.com/wiki/Transaction_Fee).
In short, transaction fees are very small amounts (on the order of ~10) of [XRP drops](https://ripple.com/wiki/Ripple_credits#Notes_on_drops) spent and destroyed with every transaction. They are largely used to account for network load and prevent spam. With `ripple-lib`, transaction fees are calculated locally by default and the fee you are willing to pay is submitted along with your transaction.
Since the fee required for a transaction may change between the time when the original fee was calculated and the time when the transaction is submitted, it is wise to use the [`fee_cushion`](REFERENCE.md#1-remote-options) to ensure that the transaction will go through. For example, suppose the original fee calculated for a transaction was 10 XRP drops but at the instant the transaction is submitted the server is experiencing a higher load and it has raised its minimum fee to 12 XRP drops. Without a `fee_cusion`, this transaction would not be processed by the server, but with a `fee_cusion` of, say, 1.5 it would be processed and you would just pay the 2 extra XRP drops.
The [`max_fee`](REFERENCE.md#1-remote-options) option can be used to avoid submitting a transaction to a server that is charging unreasonably high fees.
##4. Submitting a trade offer to the network
Submitting a trade offer to the network is similar to submitting a payment transaction. Here is an example for a trade that expires in 24 hours where you are offering to sell 1 USD in exchange for 100 XRP:
```js
/* Loading ripple-lib Remote and Amount modules in Node.js */
var Remote = require('ripple-lib').Remote;
var Amount = require('ripple-lib').Amount;
/* Loading ripple-lib Remote and Amount modules in a webpage */
// var Remote = ripple.Remote;
// var Amount = ripple.Amount;
var MY_ADDRESS = 'rrrMyAddress';
var MY_SECRET = 'secret';
var 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 */ });
remote.connect(function() {
remote.set_secret(MY_ADDRESS, MY_SECRET);
var transaction = remote.transaction();
transaction.offer_create({
from: MY_ADDRESS,
buy: BUY_AMOUNT,
sell: SELL_AMOUNT,
expiration: EXPIRATION
});
transaction.submit(function(err, res) {
/* handle submission errors / success */
});
});
```
##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

258
docs/REFERENCE.md Normal file
View File

@@ -0,0 +1,258 @@
#`ripple-lib` API Reference
__(More examples coming soon!)__
###In this document:
1. [`Remote` options](REFERENCE.md#1-remote-options)
2. [`Remote` functions](REFERENCE.md#2-remote-functions)
+ [Server info functions](REFERENCE.md#server-info-functions)
+ [Ledger query functions](REFERENCE.md#ledger-query-functions)
+ [Transaction query functions](REFERENCE.md#transaction-query-functions)
+ [Account query functions](REFERENCE.md#account-query-functions)
+ [Order book query functions](REFERENCE.md#order-book-query-functions)
+ [Transaction submission functions](REFERENCE.md#transaction-submission-functions)
3. [`Transaction` events](REFERENCE.md#3-transaction-events)
4. [`Amount` objects](REFERENCE.md#4-amount-objects)
###Also see:
1. [The `ripple-lib` README](../README.md)
2. [The `ripple-lib` GUIDES](GUIDES.md)
#1. `Remote` options
```js
/* Loading ripple-lib with Node.js */
var Remote = require('ripple-lib').Remote;
/* Loading ripple-lib in a webpage */
// var Remote = ripple.Remote;
var remote = new Remote({options});
```
A new `Remote` can be created with the following options:
+ `trace` Log all of the events emitted (boolean)
+ `max_listeners` Set maxListeners for remote; prevents EventEmitter warnings (number)
+ `connection_offset` Connect to remote servers on supplied interval (number in seconds)
+ `trusted` truthy, if remote is trusted (boolean)
+ `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))
+ `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))
+ `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))
+ `servers` Array of server objects of the following form:
```js
{
host: <string>
, port: <number>
, secure: <boolean>
}
```
#2. `Remote` functions
##Server info functions
**[request_server_info([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
var request = remote.request_server_info();
request.set_server('my.hostname');
request.callback(function(err, res) {
});
request.request();
```
**[request_unl_list([callback])](https://ripple.com/wiki/RPC_API#unl_list)**
**[request_unl_add(addr, comment, [callback])](https://ripple.com/wiki/RPC_API#unl_add)**
**[request_unl_delete(node, [callback])](https://ripple.com/wiki/RPC_API#unl_delete)**
**[request_peers([callback])](https://ripple.com/wiki/RPC_API#peers)**
**[request_connect(ip, port, [callback])](https://ripple.com/wiki/RPC_API#connect)**
##Ledger query functions
**[request_ledger(ledger, [opts], [callback])](https://ripple.com/wiki/RPC_API#ledger)**
**request_ledger_header([callback])**
**[request_ledger_current([callback])](https://ripple.com/wiki/RPC_API#ledger_current)**
**[request_ledger_entry(type, [callback])](https://ripple.com/wiki/RPC_API#ledger_entry)**
**[request_subscribe(streams, [callback])](https://ripple.com/wiki/RPC_API#subscribe)**
Start receiving selected streams from the server.
**[request_unsubscribe(streams, [callback])](https://ripple.com/wiki/RPC_API#unsubscribe)**
Stop receiving selected streams from the server.
##Transaction query functions
**[request_transaction_entry(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.
**[request_tx(hash, [callback])](https://ripple.com/wiki/RPC_API#tx)**
Searches ledger history for validated transaction hashes.
##Account query functions
**[request_account_info(account, [callback])](https://ripple.com/wiki/RPC_API#account_info)**
Return information about the specified account.
```
{
ledger_current_index: <number>,
account_data: {
Account: <string>,
Balance: <number>,
Flags: <number>,
LedgerEntryType: <string>,
OwnerCount: <number>,
PreviousTxnID: <string>,
PreviousTxnLgrSeq: <number>,
Sequence: <number> ,
index: <string>
}
}
```
**[request_account_lines(accountID, account_index, current, [callback])](https://ripple.com/wiki/RPC_API#account_lines)**
**[request_account_offers(accountID, account_index, current, [callback])](https://ripple.com/wiki/RPC_API#account_offers)**
Return the specified account's outstanding offers.
**[request_account_tx(opts, [callback])](https://ripple.com/wiki/RPC_API#account_tx)**
Fetch a list of transactions that applied to this account.
Options:
+ `account`
+ `ledger_index_min` *deprecated, -1*
+ `ledger_index_max` *deprecated, -1*
+ `binary` *false*
+ `count` *false*
+ `descending` *false*
+ `offset` *0*
+ `limit`
+ `forward` *false*
+ `fwd_marker`
+ `rev_marker`
**[request_wallet_accounts(seed, [callback])](https://ripple.com/wiki/RPC_API#wallet_accounts)**
Return a list of accounts for a wallet.
+ requires trusted remote
**request_account_balance(account, ledger, [callback])**
Get the balance for an account. Returns an [Amount](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/amount.js) object.
**request_account_flags(account, current, [callback])**
Return the flags for an account.
**request_owner_count(account, current, [callback])**
Return the owner count for an account.
**request_ripple_balance(account, issuer, currency, current, [callback])**
Return a request to get a ripple balance
##Order book query functions
**[request_book_offers(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
var request = remote.request_book_offers({
gets: {
'currency':'XRP'
},
pays: {
'currency':'USD',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
}
});
request.request();
```
##Transaction submission functions
**[request_sign(secret, tx_json, [callback])](https://ripple.com/wiki/RPC_API#sign)**
Sign a transaction.
+ requires trusted remote
**[request_submit([callback])](https://ripple.com/wiki/RPC_API#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.
**[request_ripple_path_find(src_account, dst_account, dst_amount, src_currencies, [callback])](https://ripple.com/wiki/RPC_API#path_find)**
**transaction([destination], [source], [amount], [callback])**
Returns a [Transaction](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/transaction.js) object
#3. Transaction events
[Transaction](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/transaction.js) objects are EventEmitters. They may emit the following events.
+ `final` Transaction has erred or succeeded. This event indicates that the transaction has finished processing.
+ `error` Transaction has erred. This event is a final state.
+ `success` Transaction succeeded. This event is a final state.
+ `submitted` Transaction has been submitted to the network. The submission may result in a remote error or success.
+ `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.
+ `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.
+ `abort` Transaction has been aborted. Transactions are only aborted by manual calls to `#abort`.
+ `missing` Four ledgers have closed without detecting validated transaction
+ `lost` Eight ledgers have closed without detecting validated transaction. Consider the transaction lost and err/finalize.
#4. Amount objects
Coming Soon

View File

@@ -1,38 +1,48 @@
{
"name": "ripple-lib",
"version": "0.7.11",
"version": "0.7.35",
"description": "Ripple JavaScript client library",
"files": [
"src/js/*.js",
"build/sjcl.js",
"bin/rsign.js"
"src/js/*",
"bin/*",
"build/*",
"test/*",
"Makefile",
"Gruntfile.js"
],
"main": "src/js",
"main": "src/js/ripple",
"directories": {
"test": "test"
},
"dependencies": {
"async": "~0.1.22",
"ws": "~0.4.22",
"extend": "~1.1.1",
"simple-jsonrpc": "~0.0.1"
"async": "~0.2.10",
"ws": "~0.4.31",
"extend": "~1.2.1",
"jshint-loader": "~0.5.0",
"lru-cache": "~2.5.0"
},
"devDependencies": {
"grunt": "~0.3.17",
"buster": "~0.6.2",
"grunt-webpack": "~0.4.0"
"grunt": "~0.4.1",
"grunt-cli": "~0.1.9",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-watch": "~0.4.4",
"grunt-webpack": "~0.10.5",
"grunt-dox": "~0.5.0",
"mocha": "~1.14.0",
"sinon-chai": "~2.4.0",
"sinon": "~1.7.3"
},
"scripts": {
"test": "buster test"
"pretest": "node_modules/.bin/grunt",
"test": "mocha test/*-test.js",
"build": "grunt"
},
"repository": {
"type": "git",
"url": "git://github.com/jedmccaleb/NewCoin.git"
"url": "git://github.com/ripple/ripple-lib.git"
},
"readmeFilename": "README.md"
"readmeFilename": "README.md",
"engines": {
"node": ">=0.10.0"
}
}

View File

@@ -0,0 +1,14 @@
var fs = require('fs');
var Ledger = require('../src/js/ripple/ledger').Ledger;
if (process.argc < 1) {
console.error("Usage: scripts/verify_ledger_json path/to/ledger.json");
process.exit(1);
}
var json = fs.readFileSync(process.argv[2], 'utf-8');
var ledger = Ledger.from_json(JSON.parse(json));
console.log("Calculated transaction hash: "+ledger.calc_tx_hash().to_hex())
console.log("Transaction hash in header: "+ledger.ledger_json.transaction_hash);

126
src/cpp/TransactionErr.cpp Normal file
View File

@@ -0,0 +1,126 @@
#include "TransactionErr.h"
#include "utils.h"
bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman)
{
static struct {
TER terCode;
const char* cpToken;
const char* cpHuman;
} transResultInfoA[] = {
{ tecCLAIM, "tecCLAIM", "Fee claimed. Sequence used. No action." },
{ tecDIR_FULL, "tecDIR_FULL", "Can not add entry to full directory." },
{ tecFAILED_PROCESSING, "tecFAILED_PROCESSING", "Failed to correctly process transaction." },
{ tecINSUF_RESERVE_LINE, "tecINSUF_RESERVE_LINE", "Insufficient reserve to add trust line." },
{ tecINSUF_RESERVE_OFFER, "tecINSUF_RESERVE_OFFER", "Insufficient reserve to create offer." },
{ tecNO_DST, "tecNO_DST", "Destination does not exist. Send XRP to create it." },
{ tecNO_DST_INSUF_XRP, "tecNO_DST_INSUF_XRP", "Destination does not exist. Too little XRP sent to create it." },
{ tecNO_LINE_INSUF_RESERVE, "tecNO_LINE_INSUF_RESERVE", "No such line. Too little reserve to create it." },
{ tecNO_LINE_REDUNDANT, "tecNO_LINE_REDUNDANT", "Can't set non-existant line to default." },
{ tecPATH_DRY, "tecPATH_DRY", "Path could not send partial amount." },
{ tecPATH_PARTIAL, "tecPATH_PARTIAL", "Path could not send full amount." },
{ tecUNFUNDED, "tecUNFUNDED", "One of _ADD, _OFFER, or _SEND. Deprecated." },
{ tecUNFUNDED_ADD, "tecUNFUNDED_ADD", "Insufficient XRP balance for WalletAdd." },
{ tecUNFUNDED_OFFER, "tecUNFUNDED_OFFER", "Insufficient balance to fund created offer." },
{ tecUNFUNDED_PAYMENT, "tecUNFUNDED_PAYMENT", "Insufficient XRP balance to send." },
{ tefFAILURE, "tefFAILURE", "Failed to apply." },
{ tefALREADY, "tefALREADY", "The exact transaction was already in this ledger." },
{ tefBAD_ADD_AUTH, "tefBAD_ADD_AUTH", "Not authorized to add account." },
{ tefBAD_AUTH, "tefBAD_AUTH", "Transaction's public key is not authorized." },
{ tefBAD_CLAIM_ID, "tefBAD_CLAIM_ID", "Malformed: Bad claim id." },
{ tefBAD_GEN_AUTH, "tefBAD_GEN_AUTH", "Not authorized to claim generator." },
{ tefBAD_LEDGER, "tefBAD_LEDGER", "Ledger in unexpected state." },
{ tefCLAIMED, "tefCLAIMED", "Can not claim a previously claimed account." },
{ tefCREATED, "tefCREATED", "Can't add an already created account." },
{ tefDST_TAG_NEEDED, "tefDST_TAG_NEEDED", "Destination tag required." },
{ tefEXCEPTION, "tefEXCEPTION", "Unexpected program state." },
{ tefGEN_IN_USE, "tefGEN_IN_USE", "Generator already in use." },
{ tefINTERNAL, "tefINTERNAL", "Internal error." },
{ tefNO_AUTH_REQUIRED, "tefNO_AUTH_REQUIRED", "Auth is not required." },
{ tefPAST_SEQ, "tefPAST_SEQ", "This sequence number has already past." },
{ telLOCAL_ERROR, "telLOCAL_ERROR", "Local failure." },
{ telBAD_DOMAIN, "telBAD_DOMAIN", "Domain too long." },
{ telBAD_PATH_COUNT, "telBAD_PATH_COUNT", "Malformed: Too many paths." },
{ telBAD_PUBLIC_KEY, "telBAD_PUBLIC_KEY", "Public key too long." },
{ telFAILED_PROCESSING, "telFAILED_PROCESSING", "Failed to correctly process transaction." },
{ telINSUF_FEE_P, "telINSUF_FEE_P", "Fee insufficient." },
{ telNO_DST_PARTIAL, "telNO_DST_PARTIAL", "Partial payment to create account not allowed." },
{ temMALFORMED, "temMALFORMED", "Malformed transaction." },
{ temBAD_AMOUNT, "temBAD_AMOUNT", "Can only send positive amounts." },
{ temBAD_AUTH_MASTER, "temBAD_AUTH_MASTER", "Auth for unclaimed account needs correct master key." },
{ temBAD_CURRENCY, "temBAD_CURRENCY", "Malformed: Bad currency." },
{ temBAD_FEE, "temBAD_FEE", "Invalid fee, negative or not XRP." },
{ temBAD_EXPIRATION, "temBAD_EXPIRATION", "Malformed: Bad expiration." },
{ temBAD_ISSUER, "temBAD_ISSUER", "Malformed: Bad issuer." },
{ temBAD_LIMIT, "temBAD_LIMIT", "Limits must be non-negative." },
{ temBAD_OFFER, "temBAD_OFFER", "Malformed: Bad offer." },
{ temBAD_PATH, "temBAD_PATH", "Malformed: Bad path." },
{ temBAD_PATH_LOOP, "temBAD_PATH_LOOP", "Malformed: Loop in path." },
{ temBAD_PUBLISH, "temBAD_PUBLISH", "Malformed: Bad publish." },
{ temBAD_SIGNATURE, "temBAD_SIGNATURE", "Malformed: Bad signature." },
{ temBAD_SRC_ACCOUNT, "temBAD_SRC_ACCOUNT", "Malformed: Bad source account." },
{ temBAD_TRANSFER_RATE, "temBAD_TRANSFER_RATE", "Malformed: Transfer rate must be >= 1.0" },
{ temBAD_SEQUENCE, "temBAD_SEQUENCE", "Malformed: Sequence is not in the past." },
{ temBAD_SEND_XRP_LIMIT, "temBAD_SEND_XRP_LIMIT", "Malformed: Limit quality is not allowed for XRP to XRP." },
{ temBAD_SEND_XRP_MAX, "temBAD_SEND_XRP_MAX", "Malformed: Send max is not allowed for XRP to XRP." },
{ temBAD_SEND_XRP_NO_DIRECT, "temBAD_SEND_XRP_NO_DIRECT", "Malformed: No Ripple direct is not allowed for XRP to XRP." },
{ temBAD_SEND_XRP_PARTIAL, "temBAD_SEND_XRP_PARTIAL", "Malformed: Partial payment is not allowed for XRP to XRP." },
{ temBAD_SEND_XRP_PATHS, "temBAD_SEND_XRP_PATHS", "Malformed: Paths are not allowed for XRP to XRP." },
{ temDST_IS_SRC, "temDST_IS_SRC", "Destination may not be source." },
{ temDST_NEEDED, "temDST_NEEDED", "Destination not specified." },
{ temINVALID, "temINVALID", "The transaction is ill-formed." },
{ temINVALID_FLAG, "temINVALID_FLAG", "The transaction has an invalid flag." },
{ temREDUNDANT, "temREDUNDANT", "Sends same currency to self." },
{ temREDUNDANT_SEND_MAX, "temREDUNDANT_SEND_MAX", "Send max is redundant." },
{ temRIPPLE_EMPTY, "temRIPPLE_EMPTY", "PathSet with no paths." },
{ temUNCERTAIN, "temUNCERTAIN", "In process of determining result. Never returned." },
{ temUNKNOWN, "temUNKNOWN", "The transactions requires logic not implemented yet." },
{ terRETRY, "terRETRY", "Retry transaction." },
{ terFUNDS_SPENT, "terFUNDS_SPENT", "Can't set password, password set funds already spent." },
{ terINSUF_FEE_B, "terINSUF_FEE_B", "Account balance can't pay fee." },
{ terLAST, "terLAST", "Process last." },
{ terNO_ACCOUNT, "terNO_ACCOUNT", "The source account does not exist." },
{ terNO_AUTH, "terNO_AUTH", "Not authorized to hold IOUs." },
{ terNO_LINE, "terNO_LINE", "No such line." },
{ terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction." },
{ terOWNERS, "terOWNERS", "Non-zero owner count." },
{ tesSUCCESS, "tesSUCCESS", "The transaction was applied." },
};
int iIndex = NUMBER(transResultInfoA);
while (iIndex-- && transResultInfoA[iIndex].terCode != terCode)
;
if (iIndex >= 0)
{
strToken = transResultInfoA[iIndex].cpToken;
strHuman = transResultInfoA[iIndex].cpHuman;
}
return iIndex >= 0;
}
std::string transToken(TER terCode)
{
std::string strToken;
std::string strHuman;
return transResultInfo(terCode, strToken, strHuman) ? strToken : "-";
}
std::string transHuman(TER terCode)
{
std::string strToken;
std::string strHuman;
return transResultInfo(terCode, strToken, strHuman) ? strHuman : "-";
}
// vim:ts=4

153
src/cpp/TransactionErr.h Normal file
View File

@@ -0,0 +1,153 @@
#ifndef _TRANSACTION_ERR_
#define _TRANSACTION_ERR_
#include <string>
enum TER // aka TransactionEngineResult
{
// Note: Range is stable. Exact numbers are currently unstable. Use tokens.
// -399 .. -300: L Local error (transaction fee inadequate, exceeds local limit)
// Only valid during non-consensus processing.
// Implications:
// - Not forwarded
// - No fee check
telLOCAL_ERROR = -399,
telBAD_DOMAIN,
telBAD_PATH_COUNT,
telBAD_PUBLIC_KEY,
telFAILED_PROCESSING,
telINSUF_FEE_P,
telNO_DST_PARTIAL,
// -299 .. -200: M Malformed (bad signature)
// Causes:
// - Transaction corrupt.
// Implications:
// - Not applied
// - Not forwarded
// - Reject
// - Can not succeed in any imagined ledger.
temMALFORMED = -299,
temBAD_AMOUNT,
temBAD_AUTH_MASTER,
temBAD_CURRENCY,
temBAD_FEE,
temBAD_EXPIRATION,
temBAD_ISSUER,
temBAD_LIMIT,
temBAD_OFFER,
temBAD_PATH,
temBAD_PATH_LOOP,
temBAD_PUBLISH,
temBAD_TRANSFER_RATE,
temBAD_SEND_XRP_LIMIT,
temBAD_SEND_XRP_MAX,
temBAD_SEND_XRP_NO_DIRECT,
temBAD_SEND_XRP_PARTIAL,
temBAD_SEND_XRP_PATHS,
temBAD_SIGNATURE,
temBAD_SRC_ACCOUNT,
temBAD_SEQUENCE,
temDST_IS_SRC,
temDST_NEEDED,
temINVALID,
temINVALID_FLAG,
temREDUNDANT,
temREDUNDANT_SEND_MAX,
temRIPPLE_EMPTY,
temUNCERTAIN, // An intermediate result used internally, should never be returned.
temUNKNOWN,
// -199 .. -100: F Failure (sequence number previously used)
// Causes:
// - Transaction cannot succeed because of ledger state.
// - Unexpected ledger state.
// - C++ exception.
// Implications:
// - Not applied
// - Not forwarded
// - Could succeed in an imagined ledger.
tefFAILURE = -199,
tefALREADY,
tefBAD_ADD_AUTH,
tefBAD_AUTH,
tefBAD_CLAIM_ID,
tefBAD_GEN_AUTH,
tefBAD_LEDGER,
tefCLAIMED,
tefCREATED,
tefDST_TAG_NEEDED,
tefEXCEPTION,
tefGEN_IN_USE,
tefINTERNAL,
tefNO_AUTH_REQUIRED, // Can't set auth if auth is not required.
tefPAST_SEQ,
// -99 .. -1: R Retry (sequence too high, no funds for txn fee, originating account non-existent)
// Causes:
// - Prior application of another, possibly non-existant, another transaction could allow this transaction to succeed.
// Implications:
// - Not applied
// - Not forwarded
// - Might succeed later
// - Hold
// - Makes hole in sequence which jams transactions.
terRETRY = -99,
terFUNDS_SPENT, // This is a free transaction, therefore don't burden network.
terINSUF_FEE_B, // Can't pay fee, therefore don't burden network.
terNO_ACCOUNT, // Can't pay fee, therefore don't burden network.
terNO_AUTH, // Not authorized to hold IOUs.
terNO_LINE, // Internal flag.
terOWNERS, // Can't succeed with non-zero owner count.
terPRE_SEQ, // Can't pay fee, no point in forwarding, therefore don't burden network.
terLAST, // Process after all other transactions
// 0: S Success (success)
// Causes:
// - Success.
// Implications:
// - Applied
// - Forwarded
tesSUCCESS = 0,
// 100 .. 129 C Claim fee only (ripple transaction with no good paths, pay to non-existent account, no path)
// Causes:
// - Success, but does not achieve optimal result.
// - Invalid transaction or no effect, but claim fee to use the sequence number.
// Implications:
// - Applied
// - Forwarded
// Only allowed as a return code of appliedTransaction when !tapRetry. Otherwise, treated as terRETRY.
//
// DO NOT CHANGE THESE NUMBERS: They appear in ledger meta data.
tecCLAIM = 100,
tecPATH_PARTIAL = 101,
tecUNFUNDED_ADD = 102,
tecUNFUNDED_OFFER = 103,
tecUNFUNDED_PAYMENT = 104,
tecFAILED_PROCESSING = 105,
tecDIR_FULL = 121,
tecINSUF_RESERVE_LINE = 122,
tecINSUF_RESERVE_OFFER = 123,
tecNO_DST = 124,
tecNO_DST_INSUF_XRP = 125,
tecNO_LINE_INSUF_RESERVE = 126,
tecNO_LINE_REDUNDANT = 127,
tecPATH_DRY = 128,
tecUNFUNDED = 129, // Deprecated, old ambiguous unfunded.
};
#define isTelLocal(x) ((x) >= telLOCAL_ERROR && (x) < temMALFORMED)
#define isTemMalformed(x) ((x) >= temMALFORMED && (x) < tefFAILURE)
#define isTefFailure(x) ((x) >= tefFAILURE && (x) < terRETRY)
#define isTerRetry(x) ((x) >= terRETRY && (x) < tesSUCCESS)
#define isTesSuccess(x) ((x) == tesSUCCESS)
#define isTecClaim(x) ((x) >= tecCLAIM)
bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman);
std::string transToken(TER terCode);
std::string transHuman(TER terCode);
#endif
// vim:ts=4

View File

@@ -1,3 +0,0 @@
# Ripple JavaScript library
This library lets you connect to a ripple server via websockets.

View File

@@ -1,148 +0,0 @@
// Routines for working with an account.
//
// You should not instantiate this class yourself, instead use Remote#account.
//
// Events:
// wallet_clean : True, iff the wallet has been updated.
// wallet_dirty : True, iff the wallet needs to be updated.
// balance : The current stamp balance.
// balance_proposed
//
// var network = require("./network.js");
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var Amount = require('./amount').Amount;
var UInt160 = require('./uint160').UInt160;
var extend = require('extend');
var Account = function (remote, account) {
EventEmitter.call(this);
var self = this;
this._remote = remote;
this._account = UInt160.from_json(account);
this._account_id = this._account.to_json();
this._subs = 0;
// Ledger entry object
// Important: This must never be overwritten, only extend()-ed
this._entry = {};
this.on('newListener', function (type, listener) {
if (Account.subscribe_events.indexOf(type) !== -1) {
if (!self._subs && 'open' === self._remote._online_state) {
self._remote.request_subscribe()
.accounts(self._account_id)
.request();
}
self._subs += 1;
}
});
this.on('removeListener', function (type, listener) {
if (Account.subscribe_events.indexOf(type) !== -1) {
self._subs -= 1;
if (!self._subs && 'open' === self._remote._online_state) {
self._remote.request_unsubscribe()
.accounts(self._account_id)
.request();
}
}
});
this._remote.on('prepare_subscribe', function (request) {
if (self._subs) request.accounts(self._account_id);
});
this.on('transaction', function (msg) {
var changed = false;
msg.mmeta.each(function (an) {
if (an.entryType === 'AccountRoot' &&
an.fields.Account === self._account_id) {
extend(self._entry, an.fieldsNew, an.fieldsFinal);
changed = true;
}
});
if (changed) {
self.emit('entry', self._entry);
}
});
return this;
};
util.inherits(Account, EventEmitter);
/**
* List of events that require a remote subscription to the account.
*/
Account.subscribe_events = ['transaction', 'entry'];
Account.prototype.to_json = function ()
{
return this._account.to_json();
};
/**
* Whether the AccountId is valid.
*
* Note: This does not tell you whether the account exists in the ledger.
*/
Account.prototype.is_valid = function ()
{
return this._account.is_valid();
};
/**
* Retrieve the current AccountRoot entry.
*
* To keep up-to-date with changes to the AccountRoot entry, subscribe to the
* "entry" event.
*
* @param {function (err, entry)} callback Called with the result
*/
Account.prototype.entry = function (callback)
{
var self = this;
self._remote.request_account_info(this._account_id)
.on('success', function (e) {
extend(self._entry, e.account_data);
self.emit('entry', self._entry);
if ("function" === typeof callback) {
callback(null, e);
}
})
.on('error', function (e) {
callback(e);
})
.request();
return this;
};
/**
* Notify object of a relevant transaction.
*
* This is only meant to be called by the Remote class. You should never have to
* call this yourself.
*/
Account.prototype.notifyTx = function (message)
{
// Only trigger the event if the account object is actually
// subscribed - this prevents some weird phantom events from
// occurring.
if (this._subs) {
this.emit('transaction', message);
}
};
exports.Account = Account;
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,140 +0,0 @@
var sjcl = require('../../build/sjcl');
var utils = require('./utils');
var jsbn = require('./jsbn');
var extend = require('extend');
var BigInteger = jsbn.BigInteger;
var nbi = jsbn.nbi;
var Base = {};
var alphabets = Base.alphabets = {
'ripple' : "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz",
'tipple' : "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz",
'bitcoin' : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
};
extend(Base, {
'VER_NONE' : 1,
'VER_NODE_PUBLIC' : 28,
'VER_NODE_PRIVATE' : 32,
'VER_ACCOUNT_ID' : 0,
'VER_ACCOUNT_PUBLIC' : 35,
'VER_ACCOUNT_PRIVATE' : 34,
'VER_FAMILY_GENERATOR' : 41,
'VER_FAMILY_SEED' : 33
});
var sha256 = function (bytes) {
return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes)));
};
var sha256hash = function (bytes) {
return sha256(sha256(bytes));
};
// --> input: big-endian array of bytes.
// <-- string at least as long as input.
Base.encode = function (input, alpha) {
var alphabet = alphabets[alpha || 'ripple'];
var bi_base = new BigInteger(String(alphabet.length));
var bi_q = nbi();
var bi_r = nbi();
var bi_value = new BigInteger(input);
var buffer = [];
while (bi_value.compareTo(BigInteger.ZERO) > 0)
{
bi_value.divRemTo(bi_base, bi_q, bi_r);
bi_q.copyTo(bi_value);
buffer.push(alphabet[bi_r.intValue()]);
}
var i;
for (i = 0; i != input.length && !input[i]; i += 1) {
buffer.push(alphabet[0]);
}
return buffer.reverse().join("");
};
// --> input: String
// <-- array of bytes or undefined.
Base.decode = function (input, alpha) {
var alphabet = alphabets[alpha || 'ripple'];
var bi_base = new BigInteger(String(alphabet.length));
var bi_value = nbi();
var i;
for (i = 0; i != input.length && input[i] === alphabet[0]; i += 1)
;
for (; i != input.length; i += 1) {
var v = alphabet.indexOf(input[i]);
if (v < 0)
return undefined;
var r = nbi();
r.fromInt(v);
bi_value = bi_value.multiply(bi_base).add(r);
}
// toByteArray:
// - Returns leading zeros!
// - Returns signed bytes!
var bytes = bi_value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0; });
var extra = 0;
while (extra != bytes.length && !bytes[extra])
extra += 1;
if (extra)
bytes = bytes.slice(extra);
var zeros = 0;
while (zeros !== input.length && input[zeros] === alphabet[0])
zeros += 1;
return [].concat(utils.arraySet(zeros, 0), bytes);
};
// --> input: Array
// <-- String
Base.encode_check = function (version, input, alphabet) {
var buffer = [].concat(version, input);
var check = sha256(sha256(buffer)).slice(0, 4);
return Base.encode([].concat(buffer, check), alphabet);
}
// --> input : String
// <-- NaN || BigInteger
Base.decode_check = function (version, input, alphabet) {
var buffer = Base.decode(input, alphabet);
if (!buffer || buffer[0] !== version || buffer.length < 5)
return NaN;
var computed = sha256hash(buffer.slice(0, -4)).slice(0, 4);
var checksum = buffer.slice(-4);
var i;
for (i = 0; i != 4; i += 1)
if (computed[i] !== checksum[i])
return NaN;
// We'll use the version byte to add a leading zero, this ensures JSBN doesn't
// intrepret the value as a negative number
buffer[0] = 0;
return new BigInteger(buffer.slice(0, -4), 256);
}
exports.Base = Base;

View File

@@ -1,90 +0,0 @@
var ST = require("./serializedtypes");
var REQUIRED = exports.REQUIRED = 0,
OPTIONAL = exports.OPTIONAL = 1,
DEFAULT = exports.DEFAULT = 2;
ST.Int16.id = 1;
ST.Int32.id = 2;
ST.Int64.id = 3;
ST.Hash128.id = 4;
ST.Hash256.id = 5;
ST.Amount.id = 6;
ST.VariableLength.id = 7;
ST.Account.id = 8;
ST.Object.id = 14;
ST.Array.id = 15;
ST.Int8.id = 16;
ST.Hash160.id = 17;
ST.PathSet.id = 18;
ST.Vector256.id = 19;
var base = [
[ 'TransactionType' , REQUIRED, 2, ST.Int16 ],
[ 'Flags' , OPTIONAL, 2, ST.Int32 ],
[ 'SourceTag' , OPTIONAL, 3, ST.Int32 ],
[ 'Account' , REQUIRED, 1, ST.Account ],
[ 'Sequence' , REQUIRED, 4, ST.Int32 ],
[ 'Fee' , REQUIRED, 8, ST.Amount ],
[ 'OperationLimit' , OPTIONAL, 29, ST.Int32 ],
[ 'SigningPubKey' , REQUIRED, 3, ST.VariableLength ],
[ 'TxnSignature' , OPTIONAL, 4, ST.VariableLength ]
];
exports.tx = {
AccountSet: [3].concat(base, [
[ 'EmailHash' , OPTIONAL, 1, ST.Hash128 ],
[ 'WalletLocator' , OPTIONAL, 7, ST.Hash256 ],
[ 'WalletSize' , OPTIONAL, 12, ST.Int32 ],
[ 'MessageKey' , OPTIONAL, 2, ST.VariableLength ],
[ 'Domain' , OPTIONAL, 7, ST.VariableLength ],
[ 'TransferRate' , OPTIONAL, 11, ST.Int32 ]
]),
TrustSet: [20].concat(base, [
[ 'LimitAmount' , OPTIONAL, 3, ST.Amount ],
[ 'QualityIn' , OPTIONAL, 20, ST.Int32 ],
[ 'QualityOut' , OPTIONAL, 21, ST.Int32 ]
]),
OfferCreate: [7].concat(base, [
[ 'TakerPays' , REQUIRED, 4, ST.Amount ],
[ 'TakerGets' , REQUIRED, 5, ST.Amount ],
[ 'Expiration' , OPTIONAL, 10, ST.Int32 ]
]),
OfferCancel: [8].concat(base, [
[ 'OfferSequence' , REQUIRED, 25, ST.Int32 ]
]),
SetRegularKey: [5].concat(base, [
[ 'RegularKey' , REQUIRED, 8, ST.Account ]
]),
Payment: [0].concat(base, [
[ 'Destination' , REQUIRED, 3, ST.Account ],
[ 'Amount' , REQUIRED, 1, ST.Amount ],
[ 'SendMax' , OPTIONAL, 9, ST.Amount ],
[ 'Paths' , DEFAULT , 1, ST.PathSet ],
[ 'InvoiceID' , OPTIONAL, 17, ST.Hash256 ],
[ 'DestinationTag' , OPTIONAL, 14, ST.Int32 ]
]),
Contract: [9].concat(base, [
[ 'Expiration' , REQUIRED, 10, ST.Int32 ],
[ 'BondAmount' , REQUIRED, 23, ST.Int32 ],
[ 'StampEscrow' , REQUIRED, 22, ST.Int32 ],
[ 'RippleEscrow' , REQUIRED, 17, ST.Amount ],
[ 'CreateCode' , OPTIONAL, 11, ST.VariableLength ],
[ 'FundCode' , OPTIONAL, 8, ST.VariableLength ],
[ 'RemoveCode' , OPTIONAL, 9, ST.VariableLength ],
[ 'ExpireCode' , OPTIONAL, 10, ST.VariableLength ]
]),
RemoveContract: [10].concat(base, [
[ 'Target' , REQUIRED, 7, ST.Account ]
]),
EnableFeature: [100].concat(base, [
[ 'Feature' , REQUIRED, 19, ST.Hash256 ]
]),
SetFee: [101].concat(base, [
[ 'Features' , REQUIRED, 9, ST.Array ],
[ 'BaseFee' , REQUIRED, 5, ST.Int64 ],
[ 'ReferenceFeeUnits' , REQUIRED, 30, ST.Int32 ],
[ 'ReserveBase' , REQUIRED, 31, ST.Int32 ],
[ 'ReserveIncrement' , REQUIRED, 32, ST.Int32 ]
])
};

Submodule src/js/cryptojs deleted from c3c843c513

View File

@@ -1,87 +0,0 @@
//
// Currency support
//
// XXX Internal form should be UInt160.
var Currency = function () {
// Internal form: 0 = XRP. 3 letter-code.
// XXX Internal should be 0 or hex with three letter annotation when valid.
// Json form:
// '', 'XRP', '0': 0
// 3-letter code: ...
// XXX Should support hex, C++ doesn't currently allow it.
this._value = NaN;
}
// Given "USD" return the json.
Currency.json_rewrite = function (j) {
return Currency.from_json(j).to_json();
};
Currency.from_json = function (j) {
if (j instanceof Currency) return j.clone();
else if ('string' === typeof j || 'number' === typeof j) return (new Currency()).parse_json(j);
else return new Currency(); // NaN
};
Currency.is_valid = function (j) {
return Currency.from_json(j).is_valid();
};
Currency.prototype.clone = function() {
return this.copyTo(new Currency());
};
// Returns copy.
Currency.prototype.copyTo = function (d) {
d._value = this._value;
return d;
};
Currency.prototype.equals = function (d) {
return ('string' !== typeof this._value && isNaN(this._value))
|| ('string' !== typeof d._value && isNaN(d._value)) ? false : this._value === d._value;
};
// this._value = NaN on error.
Currency.prototype.parse_json = function (j) {
if ("" === j || "0" === j || "XRP" === j) {
this._value = 0;
}
else if ('number' === typeof j) {
// XXX This is a hack
this._value = j;
}
else if ('string' != typeof j || 3 !== j.length) {
this._value = NaN;
}
else {
this._value = j;
}
return this;
};
Currency.prototype.is_native = function () {
return !isNaN(this._value) && !this._value;
};
Currency.prototype.is_valid = function () {
return 'string' === typeof this._value || !isNaN(this._value);
};
Currency.prototype.to_json = function () {
return this._value ? this._value : "XRP";
};
Currency.prototype.to_human = function () {
return this._value ? this._value : "XRP";
};
exports.Currency = Currency;
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,22 +0,0 @@
exports.Remote = require('./remote').Remote;
exports.Amount = require('./amount').Amount;
exports.Currency = require('./currency').Currency;
exports.UInt160 = require('./amount').UInt160;
exports.Seed = require('./amount').Seed;
exports.Transaction = require('./transaction').Transaction;
exports.Meta = require('./meta').Meta;
exports.utils = require('./utils');
// 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
// any time without warning.
//
// However, for programs that are tied to a specific version of ripple.js like
// the official client, it makes sense to expose the SJCL instance so we don't
// have to include it twice.
exports.sjcl = require('../../build/sjcl');
exports.config = require('./config');
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,15 +1,8 @@
// Derived from Tom Wu's jsbn code.
//
// Changes made for clean up and to package as a node.js module.
// Copyright (c) 2005-2009 Tom Wu
// Copyright (c) 2005 Tom Wu
// All Rights Reserved.
// See "LICENSE" for details.
// Basic JavaScript BN library - subset useful for RSA encryption.
// Extended JavaScript BN functions, required for RSA private ops.
// Version 1.1: new BigInteger("0", 10) returns "proper" zero
// Version 1.2: square() API, isProbablePrime fix
// Bits per digit
var dbits;
@@ -19,15 +12,15 @@ var canary = 0xdeadbeefcafe;
var j_lm = ((canary&0xffffff)==0xefcafe);
// (public) Constructor
var BigInteger = function BigInteger(a,b,c) {
function BigInteger(a,b,c) {
if(a != null)
if("number" == typeof a) this.fromNumber(a,b,c);
else if(b == null && "string" != typeof a) this.fromString(a,256);
else this.fromString(a,b);
};
}
// return new, unset BigInteger
var nbi = function nbi() { return new BigInteger(null); };
function nbi() { return new BigInteger(null); }
// am: Compute w_j += (x*this_i), propagate carries,
// c is initial carry, returns final carry.
@@ -74,7 +67,6 @@ function am3(i,x,w,j,c,n) {
}
return c;
}
if(j_lm && 'undefined' !== typeof navigator && (navigator.appName == "Microsoft Internet Explorer")) {
BigInteger.prototype.am = am2;
dbits = 30;
@@ -126,7 +118,7 @@ function bnpFromInt(x) {
this.t = 1;
this.s = (x<0)?-1:0;
if(x > 0) this[0] = x;
else if(x < -1) this[0] = x+DV;
else if(x < -1) this[0] = x+this.DV;
else this.t = 0;
}
@@ -1122,6 +1114,7 @@ function bnpMillerRabin(t) {
return true;
}
// protected
BigInteger.prototype.chunkSize = bnpChunkSize;
BigInteger.prototype.toRadix = bnpToRadix;
@@ -1137,7 +1130,31 @@ BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo;
BigInteger.prototype.modInt = bnpModInt;
BigInteger.prototype.millerRabin = bnpMillerRabin;
BigInteger.prototype.copyTo = bnpCopyTo;
BigInteger.prototype.fromInt = bnpFromInt;
BigInteger.prototype.fromString = bnpFromString;
BigInteger.prototype.clamp = bnpClamp;
BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
BigInteger.prototype.drShiftTo = bnpDRShiftTo;
BigInteger.prototype.lShiftTo = bnpLShiftTo;
BigInteger.prototype.rShiftTo = bnpRShiftTo;
BigInteger.prototype.subTo = bnpSubTo;
BigInteger.prototype.multiplyTo = bnpMultiplyTo;
BigInteger.prototype.squareTo = bnpSquareTo;
BigInteger.prototype.divRemTo = bnpDivRemTo;
BigInteger.prototype.invDigit = bnpInvDigit;
BigInteger.prototype.isEven = bnpIsEven;
BigInteger.prototype.exp = bnpExp;
// public
BigInteger.prototype.toString = bnToString;
BigInteger.prototype.negate = bnNegate;
BigInteger.prototype.abs = bnAbs;
BigInteger.prototype.compareTo = bnCompareTo;
BigInteger.prototype.bitLength = bnBitLength;
BigInteger.prototype.mod = bnMod;
BigInteger.prototype.modPowInt = bnModPowInt;
BigInteger.prototype.clone = bnClone;
BigInteger.prototype.intValue = bnIntValue;
BigInteger.prototype.byteValue = bnByteValue;
@@ -1175,6 +1192,10 @@ BigInteger.prototype.isProbablePrime = bnIsProbablePrime;
// JSBN-specific extension
BigInteger.prototype.square = bnSquare;
// "constants"
BigInteger.ZERO = nbv(0);
BigInteger.ONE = nbv(1);
// BigInteger interfaces not implemented in jsbn:
// BigInteger(int signum, byte[] magnitude)
@@ -1183,37 +1204,7 @@ BigInteger.prototype.square = bnSquare;
// int hashCode()
// long longValue()
// static BigInteger valueOf(long val)
// protected
BigInteger.prototype.copyTo = bnpCopyTo;
BigInteger.prototype.fromInt = bnpFromInt;
BigInteger.prototype.fromString = bnpFromString;
BigInteger.prototype.clamp = bnpClamp;
BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
BigInteger.prototype.drShiftTo = bnpDRShiftTo;
BigInteger.prototype.lShiftTo = bnpLShiftTo;
BigInteger.prototype.rShiftTo = bnpRShiftTo;
BigInteger.prototype.subTo = bnpSubTo;
BigInteger.prototype.multiplyTo = bnpMultiplyTo;
BigInteger.prototype.squareTo = bnpSquareTo;
BigInteger.prototype.divRemTo = bnpDivRemTo;
BigInteger.prototype.invDigit = bnpInvDigit;
BigInteger.prototype.isEven = bnpIsEven;
BigInteger.prototype.exp = bnpExp;
// public
BigInteger.prototype.toString = bnToString;
BigInteger.prototype.negate = bnNegate;
BigInteger.prototype.abs = bnAbs;
BigInteger.prototype.compareTo = bnCompareTo;
BigInteger.prototype.bitLength = bnBitLength;
BigInteger.prototype.mod = bnMod;
BigInteger.prototype.modPowInt = bnModPowInt;
BigInteger.valueOf = nbi;
// "constants"
BigInteger.ZERO = nbv(0);
BigInteger.ONE = nbv(1);
exports.nbi = nbi;
exports.BigInteger = BigInteger;
// vim:sw=2:sts=2:ts=8:et
exports.BigInteger = BigInteger;

View File

@@ -1,67 +0,0 @@
var sjcl = require('../../build/sjcl');
var UInt256 = require('./uint256').UInt256;
var KeyPair = function ()
{
this._curve = sjcl.ecc.curves['c256'];
this._secret = null;
this._pubkey = null;
};
KeyPair.from_bn_secret = function (j)
{
if (j instanceof this) {
return j.clone();
} else {
return (new this()).parse_bn_secret(j);
}
};
KeyPair.prototype.parse_bn_secret = function (j)
{
this._secret = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves['c256'], j);
return this;
};
/**
* Returns public key as sjcl public key.
*
* @private
*/
KeyPair.prototype._pub = function ()
{
var curve = this._curve;
if (!this._pubkey && this._secret) {
var exponent = this._secret._exponent;
this._pubkey = new sjcl.ecc.ecdsa.publicKey(curve, curve.G.mult(exponent));
}
return this._pubkey;
};
/**
* Returns public key as hex.
*
* Key will be returned as a compressed pubkey - 33 bytes converted to hex.
*/
KeyPair.prototype.to_hex_pub = function ()
{
var pub = this._pub();
if (!pub) return null;
var point = pub._point, y_even = point.y.mod(2).equals(0);
return sjcl.codec.hex.fromBits(sjcl.bitArray.concat(
[sjcl.bitArray.partial(8, y_even ? 0x02 : 0x03)],
point.x.toBits(this._curve.r.bitLength())
)).toUpperCase();
};
KeyPair.prototype.sign = function (hash)
{
hash = UInt256.from_json(hash);
return this._secret.signDER(hash.to_bits(), 0);
};
exports.KeyPair = KeyPair;

View File

@@ -1,58 +0,0 @@
//
// Access to the Ripple network via multiple untrusted servers or a single trusted server.
//
// Overview:
// Network configuration.
// Can leverage local storage to remember network configuration
// Aquires the network
// events:
// online
// offline
//
var remote = require("./remote.js");
var opts_default = {
DEFAULT_VALIDATORS_SITE : "redstem.com",
ips = {
}
};
//
// opts : {
// cache : undefined || {
// get : function () { return cached_value; },
// set : function (value) { cached_value = value; },
// },
//
// // Where to get validators.txt if needed.
// DEFAULT_VALIDATORS_SITE : _domain_,
//
// // Validator.txt to use.
// validators : _txt_,
// }
//
var Network = function (opts) {
};
// Set the network configuration.
Network.protocol.configure = function () {
};
// Target state: connectted
Network.protocol.start = function () {
};
// Target state: disconnect
Network.protocol.stop = function () {
};
exports.Network = Network;
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,250 +0,0 @@
// Routines for working with an orderbook.
//
// One OrderBook object represents one half of an order book. (i.e. bids OR
// asks) Which one depends on the ordering of the parameters.
//
// Events:
// - transaction A transaction that affects the order book.
// var network = require("./network.js");
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var Amount = require('./amount').Amount;
var UInt160 = require('./uint160').UInt160;
var Currency = require('./currency').Currency;
var extend = require('extend');
var OrderBook = function (remote,
currency_gets, issuer_gets,
currency_pays, issuer_pays) {
EventEmitter.call(this);
var self = this;
this._remote = remote;
this._currency_gets = currency_gets;
this._issuer_gets = issuer_gets;
this._currency_pays = currency_pays;
this._issuer_pays = issuer_pays;
this._subs = 0;
// We consider ourselves synchronized if we have a current copy of the offers,
// we are online and subscribed to updates.
this._sync = false;
// Offers
this._offers = [];
this.on('newListener', function (type, listener) {
if (OrderBook.subscribe_events.indexOf(type) !== -1) {
if (!self._subs && 'open' === self._remote._online_state) {
self._subscribe();
}
self._subs += 1;
}
});
this.on('removeListener', function (type, listener) {
if (OrderBook.subscribe_events.indexOf(type) !== -1) {
self._subs -= 1;
if (!self._subs && 'open' === self._remote._online_state) {
self._sync = false;
self._remote.request_unsubscribe()
.books([self.to_json()])
.request();
}
}
});
this._remote.on('connect', function () {
if (self._subs) {
self._subscribe();
}
});
this._remote.on('disconnect', function () {
self._sync = false;
});
return this;
};
util.inherits(OrderBook, EventEmitter);
/**
* List of events that require a remote subscription to the orderbook.
*/
OrderBook.subscribe_events = ['transaction', 'model', 'trade'];
/**
* Subscribes to orderbook.
*
* @private
*/
OrderBook.prototype._subscribe = function ()
{
var self = this;
self._remote.request_subscribe()
.books([self.to_json()], true)
.on('error', function () {
// XXX What now?
})
.on('success', function (res) {
self._sync = true;
self._offers = res.offers;
self.emit('model', self._offers);
})
.request();
};
OrderBook.prototype.to_json = function ()
{
var json = {
"taker_gets": {
"currency": this._currency_gets
},
"taker_pays": {
"currency": this._currency_pays
}
};
if (this._currency_gets !== "XRP") json["taker_gets"]["issuer"] = this._issuer_gets;
if (this._currency_pays !== "XRP") json["taker_pays"]["issuer"] = this._issuer_pays;
return json;
};
/**
* Whether the OrderBook is valid.
*
* Note: This only checks whether the parameters (currencies and issuer) are
* syntactically valid. It does not check anything against the ledger.
*/
OrderBook.prototype.is_valid = function ()
{
// XXX Should check for same currency (non-native) && same issuer
return (
Currency.is_valid(this._currency_pays) &&
(this._currency_pays === "XRP" || UInt160.is_valid(this._issuer_pays)) &&
Currency.is_valid(this._currency_gets) &&
(this._currency_gets === "XRP" || UInt160.is_valid(this._issuer_gets)) &&
!(this._currency_pays === "XRP" && this._currency_gets === "XRP")
);
};
/**
* Notify object of a relevant transaction.
*
* This is only meant to be called by the Remote class. You should never have to
* call this yourself.
*/
OrderBook.prototype.notifyTx = function (message)
{
var self = this;
var changed = false;
var trade_gets = Amount.from_json("0" + ((this._currency_gets === 'XRP') ? "" :
"/" + this._currency_gets +
"/" + this._issuer_gets));
var trade_pays = Amount.from_json("0" + ((this._currency_pays === 'XRP') ? "" :
"/" + this._currency_pays +
"/" + this._issuer_pays));
message.mmeta.each(function (an) {
if (an.entryType !== 'Offer') return;
var i, l, offer;
if (an.diffType === 'DeletedNode' ||
an.diffType === 'ModifiedNode') {
for (i = 0, l = self._offers.length; i < l; i++) {
offer = self._offers[i];
if (offer.index === an.ledgerIndex) {
if (an.diffType === 'DeletedNode') {
self._offers.splice(i, 1);
}
else extend(offer, an.fieldsFinal);
changed = true;
break;
}
}
// We don't want to count a OfferCancel as a trade
if (message.transaction.TransactionType === "OfferCancel") return;
trade_gets = trade_gets.add(an.fieldsPrev.TakerGets);
trade_pays = trade_pays.add(an.fieldsPrev.TakerPays);
if (an.diffType === 'ModifiedNode') {
trade_gets = trade_gets.subtract(an.fieldsFinal.TakerGets);
trade_pays = trade_pays.subtract(an.fieldsFinal.TakerPays);
}
} else if (an.diffType === 'CreatedNode') {
var price = Amount.from_json(an.fields.TakerPays).ratio_human(an.fields.TakerGets);
for (i = 0, l = self._offers.length; i < l; i++) {
offer = self._offers[i];
var priceItem = Amount.from_json(offer.TakerPays).ratio_human(offer.TakerGets);
if (price.compareTo(priceItem) <= 0) {
var obj = an.fields;
obj.index = an.ledgerIndex;
self._offers.splice(i, 0, an.fields);
changed = true;
break;
}
}
}
});
// Only trigger the event if the account object is actually
// subscribed - this prevents some weird phantom events from
// occurring.
if (this._subs) {
this.emit('transaction', message);
if (changed) this.emit('model', this._offers);
if (!trade_gets.is_zero()) this.emit('trade', trade_pays, trade_gets);
}
};
/**
* Get offers model asynchronously.
*
* This function takes a callback and calls it with an array containing the
* current set of offers in this order book.
*
* If the data is available immediately, the callback may be called synchronously.
*/
OrderBook.prototype.offers = function (callback)
{
var self = this;
if ("function" === typeof callback) {
if (this._sync) {
callback(this._offers);
} else {
this.once('model', function (offers) {
callback(offers);
});
}
}
return this;
};
/**
* Return latest known offers.
*
* Usually, this will just be an empty array if the order book hasn't been
* loaded yet. But this accessor may be convenient in some circumstances.
*/
OrderBook.prototype.offersSync = function ()
{
return this._offers;
};
exports.OrderBook = OrderBook;
// vim:sw=2:sts=2:ts=8:et

File diff suppressed because it is too large Load Diff

283
src/js/ripple/account.js Normal file
View File

@@ -0,0 +1,283 @@
// Routines for working with an account.
//
// You should not instantiate this class yourself, instead use Remote#account.
//
// Events:
// wallet_clean : True, iff the wallet has been updated.
// wallet_dirty : True, iff the wallet needs to be updated.
// balance: The current stamp balance.
// balance_proposed
//
// var network = require("./network.js");
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var extend = require('extend');
var Amount = require('./amount').Amount;
var UInt160 = require('./uint160').UInt160;
var TransactionManager = require('./transactionmanager').TransactionManager;
/**
* @constructor Account
* @param {Remote} remote
* @param {String} account
*/
function Account(remote, account) {
EventEmitter.call(this);
var self = this;
this._remote = remote;
this._account = UInt160.from_json(account);
this._account_id = this._account.to_json();
this._subs = 0;
// Ledger entry object
// Important: This must never be overwritten, only extend()-ed
this._entry = { };
function listenerAdded(type, listener) {
if (~Account.subscribeEvents.indexOf(type)) {
if (!self._subs && self._remote._connected) {
self._remote.request_subscribe()
.add_account(self._account_id)
.broadcast();
}
self._subs += 1;
}
};
this.on('newListener', listenerAdded);
function listenerRemoved(type, listener) {
if (~Account.subscribeEvents.indexOf(type)) {
self._subs -= 1;
if (!self._subs && self._remote._connected) {
self._remote.request_unsubscribe()
.add_account(self._account_id)
.broadcast();
}
}
};
this.on('removeListener', listenerRemoved);
function attachAccount(request) {
if (self._account.is_valid() && self._subs) {
request.add_account(self._account_id);
}
};
this._remote.on('prepare_subscribe', attachAccount);
function handleTransaction(transaction) {
if (!transaction.mmeta) return;
var changed = false;
transaction.mmeta.each(function(an) {
var isAccount = an.fields.Account === self._account_id;
var isAccountRoot = isAccount && (an.entryType === 'AccountRoot');
if (isAccountRoot) {
extend(self._entry, an.fieldsNew, an.fieldsFinal);
changed = true;
}
});
if (changed) {
self.emit('entry', self._entry);
}
};
this.on('transaction', handleTransaction);
this._transactionManager = new TransactionManager(this);
return this;
};
util.inherits(Account, EventEmitter);
/**
* List of events that require a remote subscription to the account.
*/
Account.subscribeEvents = [ 'transaction', 'entry' ];
Account.prototype.toJson = function() {
return this._account.to_json();
};
/**
* Whether the AccountId is valid.
*
* Note: This does not tell you whether the account exists in the ledger.
*/
Account.prototype.isValid = function() {
return this._account.is_valid();
};
/**
* Request account info
*
* @param {Function} callback
*/
Account.prototype.getInfo = function(callback) {
return this._remote.request_account_info(this._account_id, callback);
};
/**
* Retrieve the current AccountRoot entry.
*
* To keep up-to-date with changes to the AccountRoot entry, subscribe to the
* "entry" event.
*
* @param {Function} callback
*/
Account.prototype.entry = function(callback) {
var self = this;
var callback = typeof callback === 'function' ? callback : function(){};
function accountInfo(err, info) {
if (err) {
callback(err);
} else {
extend(self._entry, info.account_data);
self.emit('entry', self._entry);
callback(null, info);
}
};
this.getInfo(accountInfo);
return this;
};
Account.prototype.getNextSequence = function(callback) {
var callback = typeof callback === 'function' ? callback : function(){};
function accountInfo(err, info) {
if (err &&
"object" === typeof err &&
"object" === typeof err.remote &&
err.remote.error === "actNotFound") {
// New accounts will start out as sequence zero
callback(null, 0);
} else if (err) {
callback(err);
} else {
callback(null, info.account_data.Sequence);
}
};
this.getInfo(accountInfo);
return this;
};
/**
* Retrieve this account's Ripple trust lines.
*
* To keep up-to-date with changes to the AccountRoot entry, subscribe to the
* "lines" event. (Not yet implemented.)
*
* @param {function(err, lines)} callback Called with the result
*/
Account.prototype.lines = function(callback) {
var self = this;
var callback = typeof callback === 'function' ? callback : function(){};
function accountLines(err, res) {
if (err) {
callback(err);
} else {
self._lines = res.lines;
self.emit('lines', self._lines);
callback(null, res);
}
}
this._remote.requestAccountLines(this._account_id, accountLines);
return this;
};
/**
* Retrieve this account's single trust line.
*
* @param {string} currency Currency
* @param {string} address Ripple address
* @param {function(err, line)} callback Called with the result
* @returns {Account}
*/
Account.prototype.line = function(currency,address,callback) {
var self = this;
var found;
var callback = typeof callback === 'function' ? callback : function(){};
self.lines(function(err, data) {
if (err) {
callback(err);
} else {
var line = data.lines.filter(function(line) {
if (line.account === address && line.currency === currency) {
return line;
}
})[0];
callback(null, line);
}
});
return this;
};
/**
* Notify object of a relevant transaction.
*
* This is only meant to be called by the Remote class. You should never have to
* call this yourself.
*
* @param {Object} message
*/
Account.prototype.notify =
Account.prototype.notifyTx = function(transaction) {
// Only trigger the event if the account object is actually
// subscribed - this prevents some weird phantom events from
// occurring.
if (this._subs) {
this.emit('transaction', transaction);
var account = transaction.transaction.Account;
if (!account) return;
var isThisAccount = account === this._account_id;
this.emit(isThisAccount ? 'transaction-outbound' : 'transaction-inbound', transaction);
}
};
/**
* Submit a transaction to an account's
* transaction manager
*
* @param {Transaction} transaction
*/
Account.prototype.submit = function(transaction) {
this._transactionManager.submit(transaction);
};
exports.Account = Account;
// vim:sw=2:sts=2:ts=8:et

File diff suppressed because it is too large Load Diff

169
src/js/ripple/base.js Normal file
View File

@@ -0,0 +1,169 @@
var sjcl = require('./utils').sjcl;
var utils = require('./utils');
var extend = require('extend');
var BigInteger = utils.jsbn.BigInteger;
var Base = {};
var alphabets = Base.alphabets = {
ripple : "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz",
tipple : "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz",
bitcoin : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
};
extend(Base, {
VER_NONE : 1,
VER_NODE_PUBLIC : 28,
VER_NODE_PRIVATE : 32,
VER_ACCOUNT_ID : 0,
VER_ACCOUNT_PUBLIC : 35,
VER_ACCOUNT_PRIVATE : 34,
VER_FAMILY_GENERATOR : 41,
VER_FAMILY_SEED : 33
});
function sha256(bytes) {
return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes)));
};
function sha256hash(bytes) {
return sha256(sha256(bytes));
};
// --> input: big-endian array of bytes.
// <-- string at least as long as input.
Base.encode = function (input, alpha) {
var alphabet = alphabets[alpha || 'ripple'];
var bi_base = new BigInteger(String(alphabet.length));
var bi_q = new BigInteger();
var bi_r = new BigInteger();
var bi_value = new BigInteger(input);
var buffer = [];
while (bi_value.compareTo(BigInteger.ZERO) > 0) {
bi_value.divRemTo(bi_base, bi_q, bi_r);
bi_q.copyTo(bi_value);
buffer.push(alphabet[bi_r.intValue()]);
}
for (var i=0; i !== input.length && !input[i]; i += 1) {
buffer.push(alphabet[0]);
}
return buffer.reverse().join('');
};
// --> input: String
// <-- array of bytes or undefined.
Base.decode = function (input, alpha) {
if (typeof input !== 'string') {
return void(0);
}
var alphabet = alphabets[alpha || 'ripple'];
var bi_base = new BigInteger(String(alphabet.length));
var bi_value = new BigInteger();
var i;
for (i = 0; i != input.length && input[i] === alphabet[0]; i += 1)
;
for (; i !== input.length; i += 1) {
var v = alphabet.indexOf(input[i]);
if (v < 0) {
return void(0);
}
var r = new BigInteger();
r.fromInt(v);
bi_value = bi_value.multiply(bi_base).add(r);
}
// toByteArray:
// - Returns leading zeros!
// - Returns signed bytes!
var bytes = bi_value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0; });
var extra = 0;
while (extra != bytes.length && !bytes[extra]) {
extra += 1;
}
if (extra) {
bytes = bytes.slice(extra);
}
var zeros = 0;
while (zeros !== input.length && input[zeros] === alphabet[0]) {
zeros += 1;
}
return [].concat(utils.arraySet(zeros, 0), bytes);
};
Base.verify_checksum = function (bytes) {
var computed = sha256hash(bytes.slice(0, -4)).slice(0, 4);
var checksum = bytes.slice(-4);
var result = true;
for (var i=0; i<4; i++) {
if (computed[i] !== checksum[i]) {
result = false;
break;
}
}
return result;
};
// --> input: Array
// <-- String
Base.encode_check = function (version, input, alphabet) {
var buffer = [].concat(version, input);
var check = sha256(sha256(buffer)).slice(0, 4);
return Base.encode([].concat(buffer, check), alphabet);
};
// --> input : String
// <-- NaN || BigInteger
Base.decode_check = function (version, input, alphabet) {
var buffer = Base.decode(input, alphabet);
if (!buffer || buffer.length < 5) {
return NaN;
}
// Single valid version
if (typeof version === 'number' && buffer[0] !== version) {
return NaN;
}
// Multiple allowed versions
if (Array.isArray(version)) {
var match = false;
for (var i=0, l=version.length; i<l; i++) {
match |= version[i] === buffer[0];
}
if (!match) {
return NaN;
}
}
if (!Base.verify_checksum(buffer)) {
return NaN;
}
// We'll use the version byte to add a leading zero, this ensures JSBN doesn't
// intrepret the value as a negative number
buffer[0] = 0;
return new BigInteger(buffer.slice(0, -4), 256);
};
exports.Base = Base;

431
src/js/ripple/binformat.js Normal file
View File

@@ -0,0 +1,431 @@
/**
* Data type map.
*
* Mapping of type ids to data types. The type id is specified by the high
*/
var TYPES_MAP = exports.types = [
void(0),
// Common
'Int16', // 1
'Int32', // 2
'Int64', // 3
'Hash128', // 4
'Hash256', // 5
'Amount', // 6
'VL', // 7
'Account', // 8
// 9-13 reserved
void(0), // 9
void(0), // 10
void(0), // 11
void(0), // 12
void(0), // 13
'Object', // 14
'Array', // 15
// Uncommon
'Int8', // 16
'Hash160', // 17
'PathSet', // 18
'Vector256' // 19
];
/**
* Field type map.
*
* Mapping of field type id to field type name.
*/
var FIELDS_MAP = exports.fields = {
// Common types
1: { // Int16
1: 'LedgerEntryType',
2: 'TransactionType'
},
2: { // Int32
2: 'Flags',
3: 'SourceTag',
4: 'Sequence',
5: 'PreviousTxnLgrSeq',
6: 'LedgerSequence',
7: 'CloseTime',
8: 'ParentCloseTime',
9: 'SigningTime',
10: 'Expiration',
11: 'TransferRate',
12: 'WalletSize',
13: 'OwnerCount',
14: 'DestinationTag',
// Skip 15
16: 'HighQualityIn',
17: 'HighQualityOut',
18: 'LowQualityIn',
19: 'LowQualityOut',
20: 'QualityIn',
21: 'QualityOut',
22: 'StampEscrow',
23: 'BondAmount',
24: 'LoadFee',
25: 'OfferSequence',
26: 'FirstLedgerSequence',
27: 'LastLedgerSequence',
28: 'TransactionIndex',
29: 'OperationLimit',
30: 'ReferenceFeeUnits',
31: 'ReserveBase',
32: 'ReserveIncrement',
33: 'SetFlag',
34: 'ClearFlag'
},
3: { // Int64
1: 'IndexNext',
2: 'IndexPrevious',
3: 'BookNode',
4: 'OwnerNode',
5: 'BaseFee',
6: 'ExchangeRate',
7: 'LowNode',
8: 'HighNode'
},
4: { // Hash128
1: 'EmailHash'
},
5: { // Hash256
1: 'LedgerHash',
2: 'ParentHash',
3: 'TransactionHash',
4: 'AccountHash',
5: 'PreviousTxnID',
6: 'LedgerIndex',
7: 'WalletLocator',
8: 'RootIndex',
9: 'AccountTxnID',
16: 'BookDirectory',
17: 'InvoiceID',
18: 'Nickname',
19: 'Feature'
},
6: { // Amount
1: 'Amount',
2: 'Balance',
3: 'LimitAmount',
4: 'TakerPays',
5: 'TakerGets',
6: 'LowLimit',
7: 'HighLimit',
8: 'Fee',
9: 'SendMax',
16: 'MinimumOffer',
17: 'RippleEscrow',
18: 'DeliveredAmount'
},
7: { // VL
1: 'PublicKey',
2: 'MessageKey',
3: 'SigningPubKey',
4: 'TxnSignature',
5: 'Generator',
6: 'Signature',
7: 'Domain',
8: 'FundCode',
9: 'RemoveCode',
10: 'ExpireCode',
11: 'CreateCode',
12: 'MemoType',
13: 'MemoData'
},
8: { // Account
1: 'Account',
2: 'Owner',
3: 'Destination',
4: 'Issuer',
7: 'Target',
8: 'RegularKey'
},
14: { // Object
1: void(0), //end of Object
2: 'TransactionMetaData',
3: 'CreatedNode',
4: 'DeletedNode',
5: 'ModifiedNode',
6: 'PreviousFields',
7: 'FinalFields',
8: 'NewFields',
9: 'TemplateEntry',
10: 'Memo'
},
15: { // Array
1: void(0), //end of Array
2: 'SigningAccounts',
3: 'TxnSignatures',
4: 'Signatures',
5: 'Template',
6: 'Necessary',
7: 'Sufficient',
8: 'AffectedNodes',
9: 'Memos'
},
// Uncommon types
16: { // Int8
1: 'CloseResolution',
2: 'TemplateEntryType',
3: 'TransactionResult'
},
17: { // Hash160
1: 'TakerPaysCurrency',
2: 'TakerPaysIssuer',
3: 'TakerGetsCurrency',
4: 'TakerGetsIssuer'
},
18: { // PathSet
1: 'Paths'
},
19: { // Vector256
1: 'Indexes',
2: 'Hashes',
3: 'Features'
}
};
var INVERSE_FIELDS_MAP = exports.fieldsInverseMap = { };
Object.keys(FIELDS_MAP).forEach(function(k1) {
Object.keys(FIELDS_MAP[k1]).forEach(function(k2) {
INVERSE_FIELDS_MAP[FIELDS_MAP[k1][k2]] = [ Number(k1), Number(k2) ];
});
});
var REQUIRED = exports.REQUIRED = 0,
OPTIONAL = exports.OPTIONAL = 1,
DEFAULT = exports.DEFAULT = 2;
var base = [
[ 'TransactionType' , REQUIRED ],
[ 'Flags' , OPTIONAL ],
[ 'SourceTag' , OPTIONAL ],
[ 'Account' , REQUIRED ],
[ 'Sequence' , REQUIRED ],
[ 'Fee' , REQUIRED ],
[ 'OperationLimit' , OPTIONAL ],
[ 'SigningPubKey' , REQUIRED ],
[ 'TxnSignature' , OPTIONAL ]
];
exports.tx = {
AccountSet: [3].concat(base, [
[ 'EmailHash' , OPTIONAL ],
[ 'WalletLocator' , OPTIONAL ],
[ 'WalletSize' , OPTIONAL ],
[ 'MessageKey' , OPTIONAL ],
[ 'Domain' , OPTIONAL ],
[ 'TransferRate' , OPTIONAL ]
]),
TrustSet: [20].concat(base, [
[ 'LimitAmount' , OPTIONAL ],
[ 'QualityIn' , OPTIONAL ],
[ 'QualityOut' , OPTIONAL ]
]),
OfferCreate: [7].concat(base, [
[ 'TakerPays' , REQUIRED ],
[ 'TakerGets' , REQUIRED ],
[ 'Expiration' , OPTIONAL ]
]),
OfferCancel: [8].concat(base, [
[ 'OfferSequence' , REQUIRED ]
]),
SetRegularKey: [5].concat(base, [
[ 'RegularKey' , REQUIRED ]
]),
Payment: [0].concat(base, [
[ 'Destination' , REQUIRED ],
[ 'Amount' , REQUIRED ],
[ 'SendMax' , OPTIONAL ],
[ 'Paths' , DEFAULT ],
[ 'InvoiceID' , OPTIONAL ],
[ 'DestinationTag' , OPTIONAL ]
]),
Contract: [9].concat(base, [
[ 'Expiration' , REQUIRED ],
[ 'BondAmount' , REQUIRED ],
[ 'StampEscrow' , REQUIRED ],
[ 'RippleEscrow' , REQUIRED ],
[ 'CreateCode' , OPTIONAL ],
[ 'FundCode' , OPTIONAL ],
[ 'RemoveCode' , OPTIONAL ],
[ 'ExpireCode' , OPTIONAL ]
]),
RemoveContract: [10].concat(base, [
[ 'Target' , REQUIRED ]
]),
EnableFeature: [100].concat(base, [
[ 'Feature' , REQUIRED ]
]),
SetFee: [101].concat(base, [
[ 'Features' , REQUIRED ],
[ 'BaseFee' , REQUIRED ],
[ 'ReferenceFeeUnits' , REQUIRED ],
[ 'ReserveBase' , REQUIRED ],
[ 'ReserveIncrement' , REQUIRED ]
])
};
exports.ledger = {
AccountRoot: [97],
Contract: [99],
DirectoryNode: [100],
Features: [102],
GeneratorMap: [103],
LedgerHashes: [104],
Nickname: [110],
Offer: [111],
RippleState: [114],
FeeSettings: [115]
};
/*
TODO:
Need `base` factored out
AccountRoot needs AccountTxnID
{
'AccountRoot': [97,
['LedgerEntryType', REQUIRED],
['Flags', REQUIRED],
['Sequence', REQUIRED],
['PreviousTxnLgrSeq', REQUIRED],
['TransferRate', OPTIONAL],
['WalletSize', OPTIONAL],
['OwnerCount', REQUIRED],
['EmailHash', OPTIONAL],
['PreviousTxnID', REQUIRED],
['LedgerIndex', OPTIONAL],
['WalletLocator', OPTIONAL],
['Balance', REQUIRED],
['MessageKey', OPTIONAL,],
['Domain', OPTIONAL,],
['Account', REQUIRED],
['RegularKey', OPTIONAL]],
'Contract': [99,
['LedgerEntryType', REQUIRED],
['Flags', REQUIRED],
['PreviousTxnLgrSeq', REQUIRED],
['Expiration', REQUIRED],
['BondAmount', REQUIRED],
['PreviousTxnID', REQUIRED],
['LedgerIndex', OPTIONAL],
['Balance', REQUIRED],
['FundCode', OPTIONAL],
['RemoveCode', OPTIONAL],
['ExpireCode', OPTIONAL],
['CreateCode', OPTIONAL],
['Account', REQUIRED],
['Owner', REQUIRED],
['Issuer', REQUIRED]],
'DirectoryNode': [100,
['LedgerEntryType', REQUIRED],
['Flags', REQUIRED],
['IndexNext', OPTIONAL],
['IndexPrevious', OPTIONAL],
['ExchangeRate', OPTIONAL],
['LedgerIndex', OPTIONAL],
['RootIndex', REQUIRED],
['Owner', OPTIONAL],
['TakerPaysCurrency', OPTIONAL],
['TakerPaysIssuer', OPTIONAL],
['TakerGetsCurrency', OPTIONAL],
['TakerGetsIssuer', OPTIONAL],
['Indexes', REQUIRED]],
'EnabledFeatures': [102,
['LedgerEntryType', REQUIRED],
['Flags', REQUIRED],
['LedgerIndex', OPTIONAL],
['Features', REQUIRED]],
'FeeSettings': [115,
['LedgerEntryType', REQUIRED],
['Flags', REQUIRED],
['ReferenceFeeUnits', REQUIRED],
['ReserveBase', REQUIRED],
['ReserveIncrement', REQUIRED],
['BaseFee', REQUIRED],
['LedgerIndex', OPTIONAL]],
'GeneratorMap': [103,
['LedgerEntryType', REQUIRED],
['Flags', REQUIRED],
['LedgerIndex', OPTIONAL],
['Generator', REQUIRED,]],
'LedgerHashes': [104,
['LedgerEntryType', REQUIRED],
['Flags', REQUIRED],
['FirstLedgerSequence', OPTIONAL],
['LastLedgerSequence', OPTIONAL],
['LedgerIndex', OPTIONAL],
['Hashes', REQUIRED]],
'Nickname': [110,
['LedgerEntryType', REQUIRED],
['Flags', REQUIRED],
['LedgerIndex', OPTIONAL],
['MinimumOffer', OPTIONAL],
['Account', REQUIRED]],
'Offer': [111,
['LedgerEntryType', REQUIRED],
['Flags', REQUIRED],
['Sequence', REQUIRED],
['PreviousTxnLgrSeq', REQUIRED],
['Expiration', OPTIONAL],
['BookNode', REQUIRED],
['OwnerNode', REQUIRED],
['PreviousTxnID', REQUIRED],
['LedgerIndex', OPTIONAL],
['BookDirectory', REQUIRED],
['TakerPays', REQUIRED],
['TakerGets', REQUIRED],
['Account', REQUIRED]],
'RippleState': [114,
['LedgerEntryType', REQUIRED],
['Flags', REQUIRED],
['PreviousTxnLgrSeq', REQUIRED],
['HighQualityIn', OPTIONAL],
['HighQualityOut', OPTIONAL],
['LowQualityIn', OPTIONAL],
['LowQualityOut', OPTIONAL],
['LowNode', OPTIONAL],
['HighNode', OPTIONAL],
['PreviousTxnID', REQUIRED],
['LedgerIndex', OPTIONAL],
['Balance', REQUIRED],
['LowLimit', REQUIRED],
['HighLimit', REQUIRED]]
}
*/
exports.metadata = [
[ 'TransactionIndex' , REQUIRED ],
[ 'TransactionResult' , REQUIRED ],
[ 'AffectedNodes' , REQUIRED ]
];
exports.ter = {
tesSUCCESS: 0,
tecCLAIM: 100,
tecPATH_PARTIAL: 101,
tecUNFUNDED_ADD: 102,
tecUNFUNDED_OFFER: 103,
tecUNFUNDED_PAYMENT: 104,
tecFAILED_PROCESSING: 105,
tecDIR_FULL: 121,
tecINSUF_RESERVE_LINE: 122,
tecINSUF_RESERVE_OFFER: 123,
tecNO_DST: 124,
tecNO_DST_INSUF_XRP: 125,
tecNO_LINE_INSUF_RESERVE: 126,
tecNO_LINE_REDUNDANT: 127,
tecPATH_DRY: 128,
tecUNFUNDED: 129,
tecMASTER_DISABLED: 130,
tecNO_REGULAR_KEY: 131,
tecOWNERS: 132
};

230
src/js/ripple/currency.js Normal file
View File

@@ -0,0 +1,230 @@
var extend = require('extend');
var UInt160 = require('./uint160').UInt160;
var Float = require('./float').Float;
var utils = require('./utils');
//
// Currency support
//
var Currency = extend(function () {
// Internal form: 0 = XRP. 3 letter-code.
// XXX Internal should be 0 or hex with three letter annotation when valid.
// Json form:
// '', 'XRP', '0': 0
// 3-letter code: ...
// XXX Should support hex, C++ doesn't currently allow it.
this._value = NaN;
this._update();
}, UInt160);
Currency.prototype = extend({}, UInt160.prototype);
Currency.prototype.constructor = Currency;
Currency.HEX_CURRENCY_BAD = "0000000000000000000000005852500000000000";
Currency.from_json = function (j, shouldInterpretXrpAsIou) {
if (j instanceof this) {
return j.clone();
} else {
return (new this()).parse_json(j, shouldInterpretXrpAsIou);
}
};
// this._value = NaN on error.
Currency.prototype.parse_json = function (j, shouldInterpretXrpAsIou) {
this._value = NaN;
switch (typeof j) {
case 'string':
if (!j || /^(0|XRP)$/.test(j)) {
if (shouldInterpretXrpAsIou) {
this.parse_hex(Currency.HEX_CURRENCY_BAD);
} else {
this.parse_hex(Currency.HEX_ZERO);
}
} else if (/^[a-zA-Z0-9]{3}$/.test(j)) {
var currencyCode = j.toUpperCase();
var currencyData = utils.arraySet(20, 0);
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
currencyData[13] = currencyCode.charCodeAt(1) & 0xff;
currencyData[14] = currencyCode.charCodeAt(2) & 0xff;
this.parse_bytes(currencyData);
} else {
this.parse_hex(j);
}
break;
case 'number':
if (!isNaN(j)) {
this.parse_number(j);
}
break;
case 'object':
if (j instanceof Currency) {
this._value = j.copyTo({})._value;
this._update();
}
break;
}
return this;
};
/**
* Recalculate internal representation.
*
* You should never need to call this.
*/
Currency.prototype._update = function () {
var bytes = this.to_bytes();
// is it 0 everywhere except 12, 13, 14?
var isZeroExceptInStandardPositions = true;
if (!bytes) {
return "XRP";
}
this._native = false;
this._type = -1;
this._interest_start = new Date();
this._interest_period = NaN;
this._iso_code = '';
for (var i=0; i<20; i++) {
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || bytes[i]===0);
}
if (isZeroExceptInStandardPositions) {
this._iso_code = String.fromCharCode(bytes[12])
+ String.fromCharCode(bytes[13])
+ String.fromCharCode(bytes[14]);
if (this._iso_code === "\0\0\0") {
this._native = true;
this._iso_code = "XRP";
}
this._type = 0;
} else if (bytes[0] === 0x01) { // Demurrage currency
this._iso_code = String.fromCharCode(bytes[1])
+ String.fromCharCode(bytes[2])
+ String.fromCharCode(bytes[3]);
this._type = 1;
this._interest_start = (bytes[4] << 24) +
(bytes[5] << 16) +
(bytes[6] << 8) +
(bytes[7] );
this._interest_period = Float.fromBytes(bytes.slice(8, 16));
}
};
// XXX Probably not needed anymore?
/*
Currency.prototype.parse_bytes = function (byte_array) {
if (Array.isArray(byte_array) && byte_array.length === 20) {
var result;
// is it 0 everywhere except 12, 13, 14?
var isZeroExceptInStandardPositions = true;
for (var i=0; i<20; i++) {
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || byte_array[0]===0)
}
if (isZeroExceptInStandardPositions) {
var currencyCode = String.fromCharCode(byte_array[12])
+ String.fromCharCode(byte_array[13])
+ String.fromCharCode(byte_array[14]);
if (/^[A-Z0-9]{3}$/.test(currencyCode) && currencyCode !== "XRP" ) {
this._value = currencyCode;
} else if (currencyCode === "\0\0\0") {
this._value = 0;
} else {
this._value = NaN;
}
} else {
// XXX Should support non-standard currency codes
this._value = NaN;
}
} else {
this._value = NaN;
}
return this;
};
*/
Currency.prototype.is_native = function () {
return this._native;
};
/**
* Whether this currency is an interest-bearing/demurring currency.
*/
Currency.prototype.has_interest = function () {
return this._type === 1 && this._interest_start && !isNaN(this._interest_period);
};
Currency.prototype.get_interest_at = function (referenceDate) {
if (!this.has_interest) return 1;
if (referenceDate instanceof Date) {
referenceDate = utils.fromTimestamp(referenceDate.getTime());
}
return Math.pow(Math.E, (referenceDate - this._interest_start) / this._interest_period);
};
// XXX Currently we inherit UInt.prototype.is_valid, which is mostly fine.
//
// We could be doing further checks into the internal format of the
// currency data, since there are some values that are invalid.
//
//Currency.prototype.is_valid = function () {
// return this._value instanceof BigInteger && ...;
//};
Currency.prototype.to_json = function () {
if (!this.is_valid()) {
// XXX This is backwards compatible behavior, but probably not very good.
return "XRP";
}
// Any currency with standard properties and a valid code can be abbreviated
// in the JSON wire format as the three character code.
if (/^[A-Z0-9]{3}$/.test(this._iso_code) && !this.has_interest()) {
return this._iso_code;
}
// Fallback to returning the raw currency hex
var currencyHex = this.to_hex();
// XXX This is to maintain backwards compatibility, but it is very, very odd
// behavior, so we should deprecate it and get rid of it as soon as
// possible.
if (currencyHex === Currency.HEX_ONE) {
return 1;
}
return currencyHex;
};
Currency.prototype.to_human = function () {
// to_human() will always print the human-readable currency code if available.
if (/^[A-Z0-9]{3}$/.test(this._iso_code)) {
return this._iso_code;
}
return this.to_json();
};
exports.Currency = Currency;
// vim:sw=2:sts=2:ts=8:et

56
src/js/ripple/float.js Normal file
View File

@@ -0,0 +1,56 @@
/**
* IEEE 754 floating-point.
*
* Supports single- or double-precision
*/
var Float = exports.Float = {};
var allZeros = /^0+$/;
var allOnes = /^1+$/;
Float.fromBytes = function (bytes) {
// Render in binary. Hackish.
var b = "";
for (var i = 0, n = bytes.length; i < n; i++) {
var bits = (bytes[i] & 0xff).toString(2);
while (bits.length < 8) bits = "0" + bits;
b += bits;
}
// Determine configuration. This could have all been precomputed but it is fast enough.
var exponentBits = bytes.length === 4 ? 4 : 11;
var mantissaBits = (bytes.length * 8) - exponentBits - 1;
var bias = Math.pow(2, exponentBits - 1) - 1;
var minExponent = 1 - bias - mantissaBits;
// Break up the binary representation into its pieces for easier processing.
var s = b[0];
var e = b.substring(1, exponentBits + 1);
var m = b.substring(exponentBits + 1);
var value = 0;
var multiplier = (s === "0" ? 1 : -1);
if (allZeros.test(e)) {
// Zero or denormalized
if (allZeros.test(m)) {
// Value is zero
} else {
value = parseInt(m, 2) * Math.pow(2, minExponent);
}
} else if (allOnes.test(e)) {
// Infinity or NaN
if (allZeros.test(m)) {
value = Infinity;
} else {
value = NaN;
}
} else {
// Normalized
var exponent = parseInt(e, 2) - bias;
var mantissa = parseInt(m, 2);
value = (1 + (mantissa * Math.pow(2, -mantissaBits))) * Math.pow(2, exponent);
}
return value * multiplier;
};

View File

@@ -0,0 +1,23 @@
/**
* Prefix for hashing functions.
*
* These prefixes are inserted before the source material used to
* generate various hashes. This is done to put each hash in its own
* "space." This way, two different types of objects with the
* same binary data will produce different hashes.
*
* Each prefix is a 4-byte value with the last byte set to zero
* and the first three bytes formed from the ASCII equivalent of
* some arbitrary string. For example "TXN".
*/
// transaction plus signature to give transaction ID
exports.HASH_TX_ID = 0x54584E00; // 'TXN'
// transaction plus metadata
exports.HASH_TX_NODE = 0x534E4400; // 'TND'
// inner node in tree
exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN'
// inner transaction to sign
exports.HASH_TX_SIGN = 0x53545800; // 'STX'
// inner transaction to sign (TESTNET)
exports.HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'

54
src/js/ripple/index.js Normal file
View File

@@ -0,0 +1,54 @@
exports.Remote = require('./remote').Remote;
exports.Request = require('./request').Request;
exports.Amount = require('./amount').Amount;
exports.Account = require('./account').Account;
exports.Transaction = require('./transaction').Transaction;
exports.Currency = require('./currency').Currency;
exports.Base = require('./base').Base;
exports.UInt160 = require('./uint160').UInt160;
exports.UInt256 = require('./uint256').UInt256;
exports.Seed = require('./seed').Seed;
exports.Meta = require('./meta').Meta;
exports.SerializedObject = require('./serializedobject').SerializedObject;
exports.RippleError = require('./rippleerror').RippleError;
exports.binformat = require('./binformat');
exports.utils = require('./utils');
exports.Server = require('./server').Server;
// 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
// any time without warning.
//
// However, for programs that are tied to a specific version of ripple.js like
// the official client, it makes sense to expose the SJCL instance so we don't
// have to include it twice.
exports.sjcl = require('./utils').sjcl;
exports.config = require('./config');
// camelCase to under_scored API conversion
function attachUnderscored(c) {
var o = exports[c];
Object.keys(o.prototype).forEach(function(key) {
var UPPERCASE = /([A-Z]{1})[a-z]+/g;
if (!UPPERCASE.test(key)) return;
var underscored = key.replace(UPPERCASE, function(c) {
return '_' + c.toLowerCase();
});
o.prototype[underscored] = o.prototype[key];
});
};
[ 'Remote',
'Request',
'Transaction',
'Account',
'Server'
].forEach(attachUnderscored);
// vim:sw=2:sts=2:ts=8:et

96
src/js/ripple/keypair.js Normal file
View File

@@ -0,0 +1,96 @@
var sjcl = require('./utils').sjcl;
var UInt160 = require('./uint160').UInt160;
var UInt256 = require('./uint256').UInt256;
var Base = require('./base').Base;
function KeyPair() {
this._curve = sjcl.ecc.curves['c256'];
this._secret = null;
this._pubkey = null;
};
KeyPair.from_bn_secret = function (j) {
return j instanceof this ? j.clone() : (new this()).parse_bn_secret(j);
};
KeyPair.prototype.parse_bn_secret = function (j) {
this._secret = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves['c256'], j);
return this;
};
/**
* Returns public key as sjcl public key.
*
* @private
*/
KeyPair.prototype._pub = function () {
var curve = this._curve;
if (!this._pubkey && this._secret) {
var exponent = this._secret._exponent;
this._pubkey = new sjcl.ecc.ecdsa.publicKey(curve, curve.G.mult(exponent));
}
return this._pubkey;
};
/**
* Returns public key in compressed format as bit array.
*
* @private
*/
KeyPair.prototype._pub_bits = function () {
var pub = this._pub();
if (!pub) {
return null;
}
var point = pub._point, y_even = point.y.mod(2).equals(0);
return sjcl.bitArray.concat(
[sjcl.bitArray.partial(8, y_even ? 0x02 : 0x03)],
point.x.toBits(this._curve.r.bitLength())
);
};
/**
* Returns public key as hex.
*
* Key will be returned as a compressed pubkey - 33 bytes converted to hex.
*/
KeyPair.prototype.to_hex_pub = function () {
var bits = this._pub_bits();
if (!bits) {
return null;
}
return sjcl.codec.hex.fromBits(bits).toUpperCase();
};
function SHA256_RIPEMD160(bits) {
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
}
KeyPair.prototype.get_address = function () {
var bits = this._pub_bits();
if (!bits) {
return null;
}
var hash = SHA256_RIPEMD160(bits);
var address = UInt160.from_bits(hash);
address.set_version(Base.VER_ACCOUNT_ID);
return address;
};
KeyPair.prototype.sign = function (hash) {
var hash = UInt256.from_json(hash);
return this._secret.signDER(hash.to_bits(), 0);
};
exports.KeyPair = KeyPair;

40
src/js/ripple/ledger.js Normal file
View File

@@ -0,0 +1,40 @@
// Ledger
var Transaction = require('./transaction').Transaction;
var SHAMap = require('./shamap').SHAMap;
var SHAMapTreeNode = require('./shamap').SHAMapTreeNode;
var SerializedObject = require('./serializedobject').SerializedObject;
var stypes = require('./serializedtypes');
function Ledger()
{
this.ledger_json = {};
}
Ledger.from_json = function (v) {
var ledger = new Ledger();
ledger.parse_json(v);
return ledger;
};
Ledger.prototype.parse_json = function (v) {
this.ledger_json = v;
};
Ledger.prototype.calc_tx_hash = function () {
var tx_map = new SHAMap();
this.ledger_json.transactions.forEach(function (tx_json) {
var tx = Transaction.from_json(tx_json);
var meta = SerializedObject.from_json(tx_json.metaData);
var data = new SerializedObject();
stypes.VariableLength.serialize(data, tx.serialize().to_hex());
stypes.VariableLength.serialize(data, meta.to_hex());
tx_map.add_item(tx.hash(), data, SHAMapTreeNode.TYPE_TRANSACTION_MD);
});
return tx_map.hash();
};
exports.Ledger = Ledger;

103
src/js/ripple/log.js Normal file
View File

@@ -0,0 +1,103 @@
/**
* Logging functionality for ripple-lib and any applications built on it.
*/
var Log = function (namespace) {
if (!namespace) {
this._namespace = [];
} else if (Array.isArray(namespace)) {
this._namespace = namespace;
} else {
this._namespace = [""+namespace];
}
this._prefix = this._namespace.concat(['']).join(': ');
};
/**
* Create a sub-logger.
*
* You can have a hierarchy of loggers.
*
* @example
*
* var log = require('ripple').log.sub('server');
*
* log.info('connection successful');
* // prints: "server: connection successful"
*/
Log.prototype.sub = function (namespace) {
var subNamespace = this._namespace.slice();
if (namespace && "string" === typeof namespace) subNamespace.push(namespace);
var subLogger = new Log(subNamespace);
subLogger._setParent(this);
return subLogger;
};
Log.prototype._setParent = function (parentLogger) {
this._parent = parentLogger;
};
Log.makeLevel = function (level) {
return function () {
arguments[0] = this._prefix + arguments[0];
Log.engine.logObject.apply(Log, Array.prototype.slice.call(arguments));
};
};
Log.prototype.debug = Log.makeLevel(1);
Log.prototype.info = Log.makeLevel(2);
Log.prototype.warn = Log.makeLevel(3);
Log.prototype.error = Log.makeLevel(4);
/**
* Basic logging connector.
*
* This engine has no formatting and works with the most basic of "console.log"
* implementations. This is the logging engine used in Node.js.
*/
var BasicLogEngine = {
logObject: function logObject(msg) {
var args = Array.prototype.slice.call(arguments, 1);
args = args.map(function(arg) {
return JSON.stringify(arg, null, 2);
});
args.unshift(msg);
console.log.apply(console, args);
}
};
/**
* Null logging connector.
*
* This engine simply swallows all messages. Used when console.log is not
* available.
*/
var NullLogEngine = {
logObject: function () {}
};
Log.engine = NullLogEngine;
if (console && console.log) Log.engine = BasicLogEngine;
/**
* Provide a root logger as our main export.
*
* This means you can use the logger easily on the fly:
* ripple.log.debug('My object is', myObj);
*/
module.exports = new Log();
/**
* This is the logger for ripple-lib internally.
*/
module.exports.internal = module.exports.sub();
/**
* Expose the class as well.
*/
module.exports.Log = Log;

30
src/js/ripple/log.web.js Normal file
View File

@@ -0,0 +1,30 @@
var exports = module.exports = require('./log.js');
/**
* Log engine for browser consoles.
*
* Browsers tend to have better consoles that support nicely formatted
* JavaScript objects. This connector passes objects through to the logging
* function without any stringification.
*/
var InteractiveLogEngine = {
logObject: function (msg, obj) {
var args = Array.prototype.slice.call(arguments, 1);
args = args.map(function(arg) {
if (/MSIE/.test(navigator.userAgent)) {
return JSON.stringify(arg, null, 2);
} else {
return arg;
}
});
args.unshift(msg);
console.log.apply(console, args);
}
};
if (window.console && window.console.log) {
exports.Log.engine = InteractiveLogEngine;
}

View File

@@ -1,37 +1,56 @@
var extend = require('extend');
var utils = require('./utils');
var extend = require('extend');
var utils = require('./utils');
var UInt160 = require('./uint160').UInt160;
var Amount = require('./amount').Amount;
var Amount = require('./amount').Amount;
/**
* Meta data processing facility.
* Meta data processing facility
*/
var Meta = function (raw_data)
{
this.nodes = [];
for (var i = 0, l = raw_data.AffectedNodes.length; i < l; i++) {
var an = raw_data.AffectedNodes[i],
result = {};
function Meta(raw_data) {
var self = this;
["CreatedNode", "ModifiedNode", "DeletedNode"].forEach(function (x) {
if (an[x]) result.diffType = x;
});
this.nodes = [ ];
if (!result.diffType) return null;
raw_data.AffectedNodes.forEach(function(an) {
var result = { };
an = an[result.diffType];
if (result.diffType = self.diffType(an)) {
an = an[result.diffType];
result.entryType = an.LedgerEntryType;
result.ledgerIndex = an.LedgerIndex;
result.entryType = an.LedgerEntryType;
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 || {};
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;
this.nodes.push(result);
self.nodes.push(result);
}
});
};
Meta.node_types = [
'CreatedNode',
'ModifiedNode',
'DeletedNode'
];
Meta.prototype.diffType = function(an) {
var result = false;
for (var i=0; i<Meta.node_types.length; i++) {
var x = Meta.node_types[i];
if (an.hasOwnProperty(x)) {
result = x;
break;
}
}
return result;
};
/**
@@ -69,29 +88,41 @@ var Meta = function (raw_data)
* The second parameter to the callback is the index of the node in the metadata
* (first entry is index 0).
*/
Meta.prototype.each = function (fn)
{
Meta.prototype.each = function (fn) {
for (var i = 0, l = this.nodes.length; i < l; i++) {
fn(this.nodes[i], i);
}
};
([
'forEach',
'map',
'filter',
'every',
'reduce'
]).forEach(function(fn) {
Meta.prototype[fn] = function() {
return Array.prototype[fn].apply(this.nodes, arguments);
}
});
var amountFieldsAffectingIssuer = [
"LowLimit", "HighLimit", "TakerPays", "TakerGets"
'LowLimit',
'HighLimit',
'TakerPays',
'TakerGets'
];
Meta.prototype.getAffectedAccounts = function ()
{
var accounts = [];
Meta.prototype.getAffectedAccounts = function () {
var accounts = [ ];
// This code should match the behavior of the C++ method:
// TransactionMetaSet::getAffectedAccounts
this.each(function (an) {
var fields = (an.diffType === "CreatedNode") ? an.fieldsNew : an.fieldsFinal;
this.nodes.forEach(function (an) {
var fields = (an.diffType === 'CreatedNode') ? an.fieldsNew : an.fieldsFinal;
for (var i in fields) {
var field = fields[i];
if ("string" === typeof field && UInt160.is_valid(field)) {
if (typeof field === 'string' && UInt160.is_valid(field)) {
accounts.push(field);
} else if (amountFieldsAffectingIssuer.indexOf(i) !== -1) {
var amount = Amount.from_json(field);
@@ -103,16 +134,13 @@ Meta.prototype.getAffectedAccounts = function ()
}
});
accounts = utils.arrayUnique(accounts);
return accounts;
return utils.arrayUnique(accounts);
};
Meta.prototype.getAffectedBooks = function ()
{
var books = [];
Meta.prototype.getAffectedBooks = function () {
var books = [ ];
this.each(function (an) {
this.nodes.forEach(function (an) {
if (an.entryType !== 'Offer') return;
var gets = Amount.from_json(an.fields.TakerGets);
@@ -124,14 +152,17 @@ Meta.prototype.getAffectedBooks = function ()
var paysKey = pays.currency().to_json();
if (paysKey !== 'XRP') paysKey += '/' + pays.issuer().to_json();
var key = getsKey + ":" + paysKey;
var key = [ getsKey, paysKey ].join(':');
// Hell of a lot of work, so we are going to cache this. We can use this
// later to good effect in OrderBook.notify to make sure we only process
// pertinent offers.
an.bookKey = key;
books.push(key);
});
books = utils.arrayUnique(books);
return books;
return utils.arrayUnique(books);
};
exports.Meta = Meta;

296
src/js/ripple/orderbook.js Normal file
View File

@@ -0,0 +1,296 @@
// Routines for working with an orderbook.
//
// One OrderBook object represents one half of an order book. (i.e. bids OR
// asks) Which one depends on the ordering of the parameters.
//
// Events:
// - transaction A transaction that affects the order book.
// var network = require("./network.js");
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var extend = require('extend');
var Amount = require('./amount').Amount;
var UInt160 = require('./uint160').UInt160;
var Currency = require('./currency').Currency;
function OrderBook(remote, currency_gets, issuer_gets, currency_pays, issuer_pays, key) {
EventEmitter.call(this);
var self = this;
this._remote = remote;
this._currency_gets = currency_gets;
this._issuer_gets = issuer_gets;
this._currency_pays = currency_pays;
this._issuer_pays = issuer_pays;
this._subs = 0;
this._key = key;
// We consider ourselves synchronized if we have a current copy of the offers,
// we are online and subscribed to updates.
this._sync = false;
// Offers
this._offers = [ ];
function listenerAdded(type, listener) {
if (~OrderBook.subscribe_events.indexOf(type)) {
self._subs += 1;
if (self._subs == 1 && self._remote._connected) {
self._subscribe();
}
}
};
this.on('newListener', listenerAdded);
function listenerRemoved(type, listener) {
if (~OrderBook.subscribe_events.indexOf(type)) {
self._subs -= 1;
if (!self._subs && self._remote._connected) {
self._sync = false;
self._remote.request_unsubscribe()
.books([self.to_json()])
.request();
}
}
};
this.on('removeListener', listenerRemoved);
// ST: This *should* call _prepareSubscribe.
this._remote.on('prepare_subscribe', function(request) {
self._subscribe(request);
});
function remoteDisconnected() {
self._sync = false;
};
this._remote.on('disconnect', remoteDisconnected);
return this;
};
util.inherits(OrderBook, EventEmitter);
/**
* List of events that require a remote subscription to the orderbook.
*/
OrderBook.subscribe_events = ['transaction', 'model', 'trade'];
/**
* Subscribes to orderbook.
*
* @private
*/
OrderBook.prototype._subscribe = function (request) {
var self = this;
if (self.is_valid() && self._subs) {
var request = this._remote.request_subscribe();
request.addBook(self.to_json(), true);
request.once('success', function(res) {
self._sync = true;
self._offers = res.offers;
self.emit('model', self._offers);
});
request.once('error', function(err) {
// XXX What now?
});
request.request();
}
};
/**
* Adds this orderbook to a subscription request.
// ST: Currently this is not working because the server cannot give snapshots
// for more than one order book in the same subscribe message.
OrderBook.prototype._prepareSubscribe = function (request) {
var self = this;
if (self.is_valid() && self._subs) {
request.addBook(self.to_json(), true);
request.once('success', function(res) {
self._sync = true;
self._offers = res.offers;
self.emit('model', self._offers);
});
request.once('error', function(err) {
// XXX What now?
});
}
};
*/
OrderBook.prototype.to_json = function () {
var json = {
taker_gets: {
currency: this._currency_gets
},
taker_pays: {
currency: this._currency_pays
}
};
if (this._currency_gets !== 'XRP') {
json['taker_gets']['issuer'] = this._issuer_gets;
}
if (this._currency_pays !== 'XRP') {
json['taker_pays']['issuer'] = this._issuer_pays;
}
return json;
};
/**
* Whether the OrderBook is valid.
*
* Note: This only checks whether the parameters (currencies and issuer) are
* syntactically valid. It does not check anything against the ledger.
*/
OrderBook.prototype.is_valid = function () {
// XXX Should check for same currency (non-native) && same issuer
return (
Currency.is_valid(this._currency_pays) &&
(this._currency_pays === 'XRP' || UInt160.is_valid(this._issuer_pays)) &&
Currency.is_valid(this._currency_gets) &&
(this._currency_gets === 'XRP' || UInt160.is_valid(this._issuer_gets)) &&
!(this._currency_pays === 'XRP' && this._currency_gets === 'XRP')
);
};
OrderBook.prototype.trade = function(type) {
var tradeStr = '0'
+ ((this['_currency_' + type] === 'XRP') ? '' : '/'
+ this['_currency_' + type ] + '/'
+ this['_issuer_' + type]);
return Amount.from_json(tradeStr);
};
/**
* Notify object of a relevant transaction.
*
* This is only meant to be called by the Remote class. You should never have to
* call this yourself.
*/
OrderBook.prototype.notify = function (message) {
var self = this;
var changed = false;
var trade_gets = this.trade('gets');
var trade_pays = this.trade('pays');
function handleTransaction(an) {
if (an.entryType !== 'Offer' || an.bookKey !== self._key) return;
var i, l, offer;
switch(an.diffType) {
case 'DeletedNode':
case 'ModifiedNode':
var deletedNode = an.diffType === 'DeletedNode';
for (i = 0, l = self._offers.length; i < l; i++) {
offer = self._offers[i];
if (offer.index === an.ledgerIndex) {
if (deletedNode) {
self._offers.splice(i, 1);
} else {
// TODO: This assumes no fields are deleted, which is probably a
// safe assumption, but should be checked.
extend(offer, an.fieldsFinal);
}
changed = true;
break;
}
}
// We don't want to count a OfferCancel as a trade
if (message.transaction.TransactionType === 'OfferCancel') return;
trade_gets = trade_gets.add(an.fieldsPrev.TakerGets);
trade_pays = trade_pays.add(an.fieldsPrev.TakerPays);
if (!deletedNode) {
trade_gets = trade_gets.subtract(an.fieldsFinal.TakerGets);
trade_pays = trade_pays.subtract(an.fieldsFinal.TakerPays);
}
break;
case 'CreatedNode':
var price = Amount.from_json(an.fields.TakerPays).ratio_human(an.fields.TakerGets);
for (i = 0, l = self._offers.length; i < l; i++) {
offer = self._offers[i];
var priceItem = Amount.from_json(offer.TakerPays).ratio_human(offer.TakerGets);
if (price.compareTo(priceItem) <= 0) {
var obj = an.fields;
obj.index = an.ledgerIndex;
self._offers.splice(i, 0, an.fields);
changed = true;
break;
}
}
break;
}
};
message.mmeta.each(handleTransaction);
// Only trigger the event if the account object is actually
// subscribed - this prevents some weird phantom events from
// occurring.
if (this._subs) {
this.emit('transaction', message);
if (changed) {
this.emit('model', this._offers);
}
if (!trade_gets.is_zero()) {
this.emit('trade', trade_pays, trade_gets);
}
}
};
OrderBook.prototype.notifyTx = OrderBook.prototype.notify;
/**
* Get offers model asynchronously.
*
* This function takes a callback and calls it with an array containing the
* current set of offers in this order book.
*
* If the data is available immediately, the callback may be called synchronously.
*/
OrderBook.prototype.offers = function (callback) {
var self = this;
if (typeof callback === 'function') {
if (this._sync) {
callback(this._offers);
} else {
this.once('model', callback);
}
}
return this;
};
/**
* Return latest known offers.
*
* Usually, this will just be an empty array if the order book hasn't been
* loaded yet. But this accessor may be convenient in some circumstances.
*/
OrderBook.prototype.offersSync = function () {
return this._offers;
};
exports.OrderBook = OrderBook;
// vim:sw=2:sts=2:ts=8:et

86
src/js/ripple/pathfind.js Normal file
View File

@@ -0,0 +1,86 @@
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var Amount = require('./amount').Amount;
var extend = require('extend');
/**
* Represents a persistent path finding request.
*
* Only one path find request is allowed per connection, so when another path
* find request is triggered it will supercede the existing one, making it emit
* the 'end' and 'superceded' events.
*/
function PathFind(remote, src_account, dst_account, dst_amount, src_currencies) {
EventEmitter.call(this);
this.remote = remote;
this.src_account = src_account;
this.dst_account = dst_account;
this.dst_amount = dst_amount;
this.src_currencies = src_currencies;
};
util.inherits(PathFind, EventEmitter);
/**
* Submits a path_find_create request to the network.
*
* This starts a path find request, superceding all previous path finds.
*
* This will be called automatically by Remote when this object is instantiated,
* so you should only have to call it if the path find was closed or superceded
* and you wish to restart it.
*/
PathFind.prototype.create = function () {
var self = this;
var req = this.remote.request_path_find_create(this.src_account,
this.dst_account,
this.dst_amount,
this.src_currencies,
handleInitialPath);
function handleInitialPath(err, msg) {
if (err) {
self.emit('error', err);
} else {
self.notify_update(msg);
}
}
// XXX We should add ourselves to prepare_subscribe or a similar mechanism so
// that we can resubscribe after a reconnection.
req.request();
};
PathFind.prototype.close = function () {
this.remote.request_path_find_close().request();
this.emit('end');
this.emit('close');
};
PathFind.prototype.notify_update = function (message) {
var src_account = message.source_account;
var dst_account = message.destination_account;
var dst_amount = Amount.from_json(message.destination_amount);
// Only pass the event along if this path find response matches what we were
// looking for.
if (this.src_account === src_account &&
this.dst_account === dst_account &&
this.dst_amount.equals(dst_amount)) {
this.emit('update', message);
}
};
PathFind.prototype.notify_superceded = function () {
// XXX If we're set to re-subscribe whenever we connect to a new server, then
// we should cancel that behavior here. See PathFind#create.
this.emit('end');
this.emit('superceded');
};
exports.PathFind = PathFind;

1750
src/js/ripple/remote.js Normal file

File diff suppressed because it is too large Load Diff

340
src/js/ripple/request.js Normal file
View File

@@ -0,0 +1,340 @@
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var UInt160 = require('./uint160').UInt160;
var Currency = require('./currency').Currency;
var Transaction = require('./transaction').Transaction;
var Account = require('./account').Account;
var Meta = require('./meta').Meta;
var OrderBook = require('./orderbook').OrderBook;
var RippleError = require('./rippleerror').RippleError;
// Request events emitted:
// 'success' : Request successful.
// 'error' : Request failed.
// 'remoteError'
// 'remoteUnexpected'
// 'remoteDisconnected'
function Request(remote, command) {
EventEmitter.call(this);
this.remote = remote;
this.requested = false;
this.message = {
command : command,
id : void(0)
};
};
util.inherits(Request, EventEmitter);
Request.prototype.broadcast = function() {
this._broadcast = true;
return this.request();
};
// Send the request to a remote.
Request.prototype.request = function(remote) {
if (this.requested) return;
this.requested = true;
this.on('error', new Function);
this.emit('request', remote);
if (this._broadcast) {
this.remote._servers.forEach(function(server) {
this.setServer(server);
this.remote.request(this);
}, this );
} else {
this.remote.request(this);
}
return this;
};
Request.prototype.callback = function(callback, successEvent, errorEvent) {
if (callback && typeof callback === 'function') {
var self = this;
function request_success(message) {
callback.call(self, null, message);
}
function request_error(error) {
if (!(error instanceof RippleError)) {
error = new RippleError(error);
}
callback.call(self, error);
}
this.once(successEvent || 'success', request_success);
this.once(errorEvent || 'error' , request_error);
this.request();
}
return this;
};
Request.prototype.timeout = function(duration, callback) {
var self = this;
if (!this.requested) {
function requested() {
self.timeout(duration, callback);
}
this.once('request', requested);
return;
}
var emit = this.emit;
var timed_out = false;
var timeout = setTimeout(function() {
timed_out = true;
if (typeof callback === 'function') callback();
emit.call(self, 'timeout');
}, duration);
this.emit = function() {
if (!timed_out) {
clearTimeout(timeout);
emit.apply(self, arguments);
}
};
return this;
};
Request.prototype.setServer = function(server) {
var selected = null;
switch (typeof server) {
case 'object':
selected = server;
break;
case 'string':
var servers = this.remote._servers;
for (var i=0, s; s=servers[i]; i++) {
if (s._host === server) {
selected = s;
break;
}
}
break;
};
this.server = selected;
return this;
};
Request.prototype.buildPath = function(build) {
if (this.remote.local_signing) {
throw new Error(
'`build_path` is completely ignored when doing local signing as ' +
'`Paths` is a component of the signed blob. The `tx_blob` is signed,' +
'sealed and delivered, and the txn unmodified after' );
}
if (build) {
this.message.build_path = true;
} else {
// ND: rippled currently intreprets the mere presence of `build_path` as the
// value being `truthy`
delete this.message.build_path
}
return this;
};
Request.prototype.ledgerChoose = function(current) {
if (current) {
this.message.ledger_index = this.remote._ledger_current_index;
} else {
this.message.ledger_hash = this.remote._ledger_hash;
}
return this;
};
// Set the ledger for a request.
// - ledger_entry
// - transaction_entry
Request.prototype.ledgerHash = function(hash) {
this.message.ledger_hash = hash;
return this;
};
// Set the ledger_index for a request.
// - ledger_entry
Request.prototype.ledgerIndex = function(ledger_index) {
this.message.ledger_index = ledger_index;
return this;
};
Request.prototype.ledgerSelect = function(ledger_spec) {
switch (ledger_spec) {
case 'current':
case 'closed':
case 'verified':
this.message.ledger_index = ledger_spec;
break;
default:
if (Number(ledger_spec)) {
this.message.ledger_index = ledger_spec;
} else {
this.message.ledger_hash = ledger_spec;
}
break;
}
return this;
};
Request.prototype.accountRoot = function(account) {
this.message.account_root = UInt160.json_rewrite(account);
return this;
};
Request.prototype.index = function(hash) {
this.message.index = hash;
return this;
};
// Provide the information id an offer.
// --> account
// --> seq : sequence number of transaction creating offer (integer)
Request.prototype.offerId = function(account, sequence) {
this.message.offer = {
account: UInt160.json_rewrite(account),
seq: sequence
};
return this;
};
// --> index : ledger entry index.
Request.prototype.offerIndex = function(index) {
this.message.offer = index;
return this;
};
Request.prototype.secret = function(secret) {
if (secret) {
this.message.secret = secret;
}
return this;
};
Request.prototype.txHash = function(hash) {
this.message.tx_hash = hash;
return this;
};
Request.prototype.txJson = function(json) {
this.message.tx_json = json;
return this;
};
Request.prototype.txBlob = function(json) {
this.message.tx_blob = json;
return this;
};
Request.prototype.rippleState = function(account, issuer, currency) {
this.message.ripple_state = {
currency : currency,
accounts : [
UInt160.json_rewrite(account),
UInt160.json_rewrite(issuer)
]
};
return this;
};
Request.prototype.accounts = function(accounts, proposed) {
if (!Array.isArray(accounts)) {
accounts = [ accounts ];
}
// Process accounts parameters
var processedAccounts = accounts.map(function(account) {
return UInt160.json_rewrite(account);
});
if (proposed) {
this.message.accounts_proposed = processedAccounts;
} else {
this.message.accounts = processedAccounts;
}
return this;
};
Request.prototype.addAccount = function(account, proposed) {
var processedAccount = UInt160.json_rewrite(account);
if (proposed) {
this.message.accounts_proposed = (this.message.accounts_proposed || []).concat(processedAccount);
} else {
this.message.accounts = (this.message.accounts || []).concat(processedAccount);
}
return this;
};
Request.prototype.rtAccounts =
Request.prototype.accountsProposed = function(accounts) {
return this.accounts(accounts, true);
};
Request.prototype.addAccountProposed = function(account) {
return this.addAccount(account, true);
};
Request.prototype.books = function(books, snapshot) {
// Reset list of books (this method overwrites the current list)
this.message.books = [ ];
for (var i = 0, l = books.length; i < l; i++) {
var book = books[i];
this.addBook(book, snapshot);
}
return this;
};
Request.prototype.addBook = function (book, snapshot) {
if (!Array.isArray(this.message.books)) {
this.message.books = [];
}
var json = { };
function processSide(side) {
if (!book[side]) {
throw new Error('Missing ' + side);
}
var obj = json[side] = {
currency: Currency.json_rewrite(book[side].currency)
};
if (obj.currency !== 'XRP') {
obj.issuer = UInt160.json_rewrite(book[side].issuer);
}
}
[ 'taker_gets', 'taker_pays' ].forEach(processSide);
if (snapshot) {
json.snapshot = true;
}
if (book.both) {
json.both = true;
}
this.message.books.push(json);
};
exports.Request = Request;

View File

@@ -0,0 +1,30 @@
var util = require('util');
var extend = require('extend');
function RippleError(code, message) {
switch (typeof code) {
case 'object':
extend(this, code);
break;
case 'string':
this.result = code;
this.result_message = message;
break;
}
this.engine_result = this.result = (this.result || this.engine_result || this.error || 'Error');
this.engine_result_message = this.result_message = (this.result_message || this.engine_result_message || this.error_message || 'Error');
this.result_message = this.message = (this.result_message);
var stack;
if (!!Error.captureStackTrace)
Error.captureStackTrace(this, code || this);
else if (stack = new Error().stack)
this.stack = stack;
}
util.inherits(RippleError, Error);
RippleError.prototype.name = 'RippleError';
exports.RippleError = RippleError;

View File

@@ -2,17 +2,16 @@
// Seed support
//
var sjcl = require('../../build/sjcl');
var utils = require('./utils');
var jsbn = require('./jsbn');
var sjcl = utils.sjcl;
var extend = require('extend');
var BigInteger = jsbn.BigInteger;
var BigInteger = utils.jsbn.BigInteger;
var Base = require('./base').Base,
UInt = require('./uint').UInt,
UInt256 = require('./uint256').UInt256,
KeyPair = require('./keypair').KeyPair;
var Base = require('./base').Base;
var UInt = require('./uint').UInt;
var UInt256 = require('./uint256').UInt256;
var KeyPair = require('./keypair').KeyPair;
var Seed = extend(function () {
// Internal form: NaN or BigInteger
@@ -27,7 +26,7 @@ Seed.prototype.constructor = Seed;
// value = NaN on error.
// One day this will support rfc1751 too.
Seed.prototype.parse_json = function (j) {
if ('string' === typeof j) {
if (typeof j === 'string') {
if (!j.length) {
this._value = NaN;
// XXX Should actually always try and continue if it failed.
@@ -47,7 +46,7 @@ Seed.prototype.parse_json = function (j) {
};
Seed.prototype.parse_passphrase = function (j) {
if ("string" !== typeof j) {
if (typeof j !== 'string') {
throw new Error("Passphrase must be a string");
}
@@ -60,8 +59,9 @@ Seed.prototype.parse_passphrase = function (j) {
};
Seed.prototype.to_json = function () {
if (!(this._value instanceof BigInteger))
if (!(this._value instanceof BigInteger)) {
return NaN;
}
var output = Base.encode_check(Base.VER_FAMILY_SEED, this.to_bytes());
@@ -70,18 +70,18 @@ Seed.prototype.to_json = function () {
function append_int(a, i) {
return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff);
}
};
function firstHalfOfSHA512(bytes) {
return sjcl.bitArray.bitSlice(
sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)),
0, 256
);
}
};
function SHA256_RIPEMD160(bits) {
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
}
};
Seed.prototype.get_key = function (account_id) {
if (!this.is_valid()) {
@@ -89,11 +89,10 @@ Seed.prototype.get_key = function (account_id) {
}
// XXX Should loop over keys until we find the right one
var private_gen, public_gen;
var curve = this._curve;
var seq = 0, i = 0;
var seq = 0;
var private_gen, public_gen, i = 0;
do {
private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.to_bytes(), i)));
i++;
@@ -103,6 +102,7 @@ Seed.prototype.get_key = function (account_id) {
var sec;
i = 0;
do {
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i)));
i++;

View File

@@ -0,0 +1,299 @@
var binformat = require('./binformat');
var extend = require('extend');
var stypes = require('./serializedtypes');
var UInt256 = require('./uint256').UInt256;
var assert = require('assert');
var utils = require('./utils');
var sjcl = utils.sjcl;
var BigInteger = utils.jsbn.BigInteger;
var TRANSACTION_TYPES = { };
Object.keys(binformat.tx).forEach(function(key) {
TRANSACTION_TYPES[binformat.tx[key][0]] = key;
});
var LEDGER_ENTRY_TYPES = {};
Object.keys(binformat.ledger).forEach(function(key) {
LEDGER_ENTRY_TYPES[binformat.ledger[key][0]] = key;
});
var TRANSACTION_RESULTS = {};
Object.keys(binformat.ter).forEach(function(key) {
TRANSACTION_RESULTS[binformat.ter[key]] = key;
});
function SerializedObject(buf) {
if (Array.isArray(buf) || (Buffer && Buffer.isBuffer(buf)) ) {
this.buffer = buf;
} else if (typeof buf === 'string') {
this.buffer = sjcl.codec.bytes.fromBits(sjcl.codec.hex.toBits(buf));
} else if (!buf) {
this.buffer = [];
} else {
throw new Error('Invalid buffer passed.');
}
this.pointer = 0;
};
SerializedObject.from_json = function (obj) {
// Create a copy of the object so we don't modify it
var obj = extend({}, obj);
var so = new SerializedObject;
var typedef;
if ("number" === typeof obj.TransactionType) {
obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType);
if (!obj.TransactionType) {
throw new Error('Transaction type ID is invalid.');
}
}
if ("string" === typeof obj.TransactionType) {
typedef = binformat.tx[obj.TransactionType];
if (!Array.isArray(typedef)) {
throw new Error('Transaction type is invalid');
}
typedef = typedef.slice();
obj.TransactionType = typedef.shift();
} else if ("undefined" !== typeof obj.LedgerEntryType) {
// XXX: TODO
throw new Error('Ledger entry binary format not yet implemented.');
} else if ("object" === typeof obj.AffectedNodes) {
typedef = binformat.metadata;
} else {
throw new Error('Object to be serialized must contain either' +
' TransactionType, LedgerEntryType or AffectedNodes.');
}
// ND: This from_*json* seems a reasonable place to put validation of `json`
SerializedObject.check_no_missing_fields(typedef, obj);
so.serialize(typedef, obj);
return so;
};
SerializedObject.check_no_missing_fields = function (typedef, obj) {
var missing_fields = [];
for (var i = typedef.length - 1; i >= 0; i--) {
var spec = typedef[i];
var field = spec[0]
var requirement = spec[1];
if (binformat.REQUIRED === requirement && obj[field] == null) {
missing_fields.push(field);
};
};
if (missing_fields.length > 0) {
var object_name;
if (obj.TransactionType != null) {
object_name = SerializedObject.lookup_type_tx(obj.TransactionType);
} else {
object_name = "TransactionMetaData";
} /*else {
TODO: LedgerEntryType ...
}*/
throw new Error(object_name + " is missing fields: " +
JSON.stringify(missing_fields));
};
}
SerializedObject.prototype.append = function (bytes) {
if (bytes instanceof SerializedObject) {
bytes = bytes.buffer;
}
this.buffer = this.buffer.concat(bytes);
this.pointer += bytes.length;
};
SerializedObject.prototype.resetPointer = function () {
this.pointer = 0;
};
function readOrPeek(advance) {
return function(bytes) {
var start = this.pointer;
var end = start + bytes;
if (end > this.buffer.length) {
throw new Error('Buffer length exceeded');
}
var result = this.buffer.slice(start, end);
if (advance) {
this.pointer = end;
}
return result;
}
};
SerializedObject.prototype.read = readOrPeek(true);
SerializedObject.prototype.peek = readOrPeek(false);
SerializedObject.prototype.to_bits = function () {
return sjcl.codec.bytes.toBits(this.buffer);
};
SerializedObject.prototype.to_hex = function () {
return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase();
};
SerializedObject.prototype.to_json = function() {
var old_pointer = this.pointer;
this.resetPointer();
var output = { };
while (this.pointer < this.buffer.length) {
var key_and_value = stypes.parse(this);
var key = key_and_value[0];
var value = key_and_value[1];
output[key] = SerializedObject.jsonify_structure(value, key);
}
this.pointer = old_pointer;
return output;
}
SerializedObject.jsonify_structure = function(structure, field_name) {
var output;
switch (typeof structure) {
case 'number':
switch (field_name) {
case 'LedgerEntryType':
output = LEDGER_ENTRY_TYPES[structure];
break;
case 'TransactionResult':
output = TRANSACTION_RESULTS[structure];
break;
case 'TransactionType':
output = TRANSACTION_TYPES[structure];
break;
default:
output = structure;
}
break;
case 'object':
if (!structure) break; //null
if (typeof structure.to_json === 'function') {
output = structure.to_json();
} else if (structure instanceof BigInteger) {
output = structure.toString(16).toUpperCase();
} else {
output = new structure.constructor; //new Array or Object
var keys = Object.keys(structure);
for (var i=0, l=keys.length; i<l; i++) {
var key = keys[i];
output[key] = SerializedObject.jsonify_structure(structure[key], key);
}
}
break;
default:
output = structure;
}
return output;
};
SerializedObject.prototype.serialize = function (typedef, obj) {
// Serialize object without end marker
stypes.Object.serialize(this, obj, true);
// ST: Old serialization
/*
// Ensure canonical order
typedef = SerializedObject.sort_typedef(typedef);
// Serialize fields
for (var i=0, l=typedef.length; i<l; i++) {
this.serialize_field(typedef[i], obj);
}
*/
};
SerializedObject.prototype.hash = function (prefix) {
var sign_buffer = new SerializedObject();
stypes.Int32.serialize(sign_buffer, prefix);
sign_buffer.append(this.buffer);
return sign_buffer.hash_sha512_half();
};
// DEPRECATED
SerializedObject.prototype.signing_hash = SerializedObject.prototype.hash;
SerializedObject.prototype.hash_sha512_half = function () {
var bits = sjcl.codec.bytes.toBits(this.buffer);
var hash = sjcl.bitArray.bitSlice(sjcl.hash.sha512.hash(bits), 0, 256);
return UInt256.from_hex(sjcl.codec.hex.fromBits(hash));
};
SerializedObject.prototype.serialize_field = function (spec, obj) {
var name = spec[0];
var presence = spec[1];
var field_id = spec[2];
var Type = stypes[spec[3]];
if (typeof obj[name] !== 'undefined') {
// ST: Old serialization code
//this.append(SerializedObject.get_field_header(Type.id, field_id));
try {
// ST: Old serialization code
//Type.serialize(this, obj[name]);
stypes.serialize(this, name, obj[name]);
} catch (e) {
// Add field name to message and rethrow
e.message = 'Error serializing "' + name + '": ' + e.message;
throw e;
}
} else if (presence === binformat.REQUIRED) {
throw new Error('Missing required field ' + name);
}
};
SerializedObject.get_field_header = function (type_id, field_id) {
var buffer = [ 0 ];
if (type_id > 0xF) {
buffer.push(type_id & 0xFF);
} else {
buffer[0] += (type_id & 0xF) << 4;
}
if (field_id > 0xF) {
buffer.push(field_id & 0xFF);
} else {
buffer[0] += field_id & 0xF;
}
return buffer;
};
SerializedObject.sort_typedef = function (typedef) {
assert(Array.isArray(typedef));
function sort_field_compare(a, b) {
// Sort by type id first, then by field id
return a[3] !== b[3] ? stypes[a[3]].id - stypes[b[3]].id : a[2] - b[2];
};
return typedef.sort(sort_field_compare);
};
SerializedObject.lookup_type_tx = function (id) {
assert(typeof id === 'number');
return TRANSACTION_TYPES[id];
};
exports.SerializedObject = SerializedObject;

View File

@@ -0,0 +1,730 @@
/**
* Type definitions for binary format.
*
* This file should not be included directly. Instead, find the format you're
* trying to parse or serialize in binformat.js and pass that to
* SerializedObject.parse() or SerializedObject.serialize().
*/
var assert = require('assert');
var extend = require('extend');
var binformat = require('./binformat');
var utils = require('./utils');
var sjcl = utils.sjcl;
var UInt128 = require('./uint128').UInt128;
var UInt160 = require('./uint160').UInt160;
var UInt256 = require('./uint256').UInt256;
var Base = require('./base').Base;
var amount = require('./amount');
var Amount = amount.Amount;
var Currency = amount.Currency;
// Shortcuts
var hex = sjcl.codec.hex;
var bytes = sjcl.codec.bytes;
var BigInteger = utils.jsbn.BigInteger;
var SerializedType = function (methods) {
extend(this, methods);
};
function isNumber(val) {
return typeof val === 'number' && isFinite(val);
};
function isString(val) {
return typeof val === 'string';
};
function isHexInt64String(val) {
return isString(val) && /^[0-9A-F]{0,16}$/i.test(val);
};
function isCurrencyString(val) {
return isString(val) && /^[A-Z0-9]{3}$/.test(val);
};
function isBigInteger(val) {
return val instanceof BigInteger;
};
function serialize_hex(so, hexData, noLength) {
var byteData = bytes.fromBits(hex.toBits(hexData));
if (!noLength) {
SerializedType.serialize_varint(so, byteData.length);
}
so.append(byteData);
};
/**
* parses bytes as hex
*/
function convert_bytes_to_hex (byte_array) {
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array)).toUpperCase();
};
SerializedType.serialize_varint = function (so, val) {
if (val < 0) {
throw new Error('Variable integers are unsigned.');
}
if (val <= 192) {
so.append([val]);
} else if (val <= 12480) {
val -= 193;
so.append([193 + (val >>> 8), val & 0xff]);
} else if (val <= 918744) {
val -= 12481;
so.append([
241 + (val >>> 16),
val >>> 8 & 0xff,
val & 0xff
]);
} else {
throw new Error('Variable integer overflow.');
}
};
SerializedType.prototype.parse_varint = function (so) {
var b1 = so.read(1)[0], b2, b3;
var result;
if (b1 > 254) {
throw new Error('Invalid varint length indicator');
}
if (b1 <= 192) {
result = b1;
} else if (b1 <= 240) {
b2 = so.read(1)[0];
result = 193 + (b1 - 193) * 256 + b2;
} else if (b1 <= 254) {
b2 = so.read(1)[0];
b3 = so.read(1)[0];
result = 12481 + (b1 - 241) * 65536 + b2 * 256 + b3
}
return result;
};
// In the following, we assume that the inputs are in the proper range. Is this correct?
// Helper functions for 1-, 2-, and 4-byte integers.
/**
* Convert an integer value into an array of bytes.
*
* The result is appended to the serialized object ('so').
*/
function append_byte_array(so, val, bytes) {
if (!isNumber(val)) {
throw new Error('Value is not a number');
}
if (val < 0 || val >= Math.pow(256, bytes)) {
throw new Error('Value out of bounds');
}
var newBytes = [ ];
for (var i=0; i<bytes; i++) {
newBytes.unshift(val >>> (i * 8) & 0xff);
}
so.append(newBytes);
};
// Convert a certain number of bytes from the serialized object ('so') into an integer.
function readAndSum(so, bytes) {
var sum = 0;
if (bytes > 4) {
throw new Error("This function only supports up to four bytes.");
}
for (var i=0; i<bytes; i++) {
var byte = so.read(1)[0];
sum += (byte << (8 * (bytes - i - 1)));
}
// Convert to unsigned integer
return sum >>> 0;
};
var STInt8 = exports.Int8 = new SerializedType({
serialize: function (so, val) {
append_byte_array(so, val, 1);
},
parse: function (so) {
return readAndSum(so, 1);
}
});
STInt8.id = 16;
var STInt16 = exports.Int16 = new SerializedType({
serialize: function (so, val) {
append_byte_array(so, val, 2);
},
parse: function (so) {
return readAndSum(so, 2);
}
});
STInt16.id = 1;
var STInt32 = exports.Int32 = new SerializedType({
serialize: function (so, val) {
append_byte_array(so, val, 4);
},
parse: function (so) {
return readAndSum(so, 4);
}
});
STInt32.id = 2;
var STInt64 = exports.Int64 = new SerializedType({
serialize: function (so, val) {
var bigNumObject;
if (isNumber(val)) {
val = Math.floor(val);
if (val < 0) {
throw new Error('Negative value for unsigned Int64 is invalid.');
}
bigNumObject = new BigInteger(String(val), 10);
} else if (isString(val)) {
if (!isHexInt64String(val)) {
throw new Error('Not a valid hex Int64.');
}
bigNumObject = new BigInteger(val, 16);
} else if (isBigInteger(val)) {
if (val.compareTo(BigInteger.ZERO) < 0) {
throw new Error('Negative value for unsigned Int64 is invalid.');
}
bigNumObject = val;
} else {
throw new Error('Invalid type for Int64');
}
var hex = bigNumObject.toString(16);
if (hex.length > 16) {
throw new Error('Int64 is too large');
}
while (hex.length < 16) {
hex = '0' + hex;
}
serialize_hex(so, hex, true); //noLength = true
},
parse: function (so) {
var result = new BigInteger(so.read(8), 256);
assert(result instanceof BigInteger);
return result;
}
});
STInt64.id = 3;
var STHash128 = exports.Hash128 = new SerializedType({
serialize: function (so, val) {
var hash = UInt128.from_json(val);
if (!hash.is_valid()) {
throw new Error('Invalid Hash128');
}
serialize_hex(so, hash.to_hex(), true); //noLength = true
},
parse: function (so) {
return UInt128.from_bytes(so.read(16));
}
});
STHash128.id = 4;
var STHash256 = exports.Hash256 = new SerializedType({
serialize: function (so, val) {
var hash = UInt256.from_json(val);
if (!hash.is_valid()) {
throw new Error('Invalid Hash256');
}
serialize_hex(so, hash.to_hex(), true); //noLength = true
},
parse: function (so) {
return UInt256.from_bytes(so.read(32));
}
});
STHash256.id = 5;
var STHash160 = exports.Hash160 = new SerializedType({
serialize: function (so, val) {
var hash = UInt160.from_json(val);
if (!hash.is_valid()) {
throw new Error('Invalid Hash160');
}
serialize_hex(so, hash.to_hex(), true); //noLength = true
},
parse: function (so) {
return UInt160.from_bytes(so.read(20));
}
});
STHash160.id = 17;
// Internal
var STCurrency = new SerializedType({
serialize: function (so, val, xrp_as_ascii) {
var currencyData = val.to_bytes();
if (!currencyData) {
throw new Error('Tried to serialize invalid/unimplemented currency type.');
}
so.append(currencyData);
},
parse: function (so) {
var bytes = so.read(20);
var currency = Currency.from_bytes(bytes);
// XXX Disabled check. Theoretically, the Currency class should support any
// UInt160 value and consider it valid. But it doesn't, so for the
// deserialization to be usable, we need to allow invalid results for now.
//if (!currency.is_valid()) {
// throw new Error('Invalid currency: '+convert_bytes_to_hex(bytes));
//}
return currency;
}
});
var STAmount = exports.Amount = new SerializedType({
serialize: function (so, val) {
var amount = Amount.from_json(val);
if (!amount.is_valid()) {
throw new Error('Not a valid Amount object.');
}
// Amount (64-bit integer)
var valueBytes = utils.arraySet(8, 0);
if (amount.is_native()) {
var valueHex = amount._value.toString(16);
// Enforce correct length (64 bits)
if (valueHex.length > 16) {
throw new Error('Value out of bounds');
}
while (valueHex.length < 16) {
valueHex = '0' + valueHex;
}
valueBytes = bytes.fromBits(hex.toBits(valueHex));
// Clear most significant two bits - these bits should already be 0 if
// Amount enforces the range correctly, but we'll clear them anyway just
// so this code can make certain guarantees about the encoded value.
valueBytes[0] &= 0x3f;
if (!amount.is_negative()) valueBytes[0] |= 0x40;
} else {
var hi = 0, lo = 0;
// First bit: non-native
hi |= 1 << 31;
if (!amount.is_zero()) {
// Second bit: non-negative?
if (!amount.is_negative()) hi |= 1 << 30;
// Next eight bits: offset/exponent
hi |= ((97 + amount._offset) & 0xff) << 22;
// Remaining 52 bits: mantissa
hi |= amount._value.shiftRight(32).intValue() & 0x3fffff;
lo = amount._value.intValue() & 0xffffffff;
}
valueBytes = sjcl.codec.bytes.fromBits([hi, lo]);
}
so.append(valueBytes);
if (!amount.is_native()) {
// Currency (160-bit hash)
var currency = amount.currency();
STCurrency.serialize(so, currency, true);
// Issuer (160-bit hash)
so.append(amount.issuer().to_bytes());
}
},
parse: function (so) {
var amount = new Amount();
var value_bytes = so.read(8);
var is_zero = !(value_bytes[0] & 0x7f);
for (var i=1; i<8; i++) {
is_zero = is_zero && !value_bytes[i];
}
if (value_bytes[0] & 0x80) {
//non-native
var currency = STCurrency.parse(so);
var issuer_bytes = so.read(20);
var issuer = UInt160.from_bytes(issuer_bytes);
issuer.set_version(Base.VER_ACCOUNT_ID);
var offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97;
var mantissa_bytes = value_bytes.slice(1);
mantissa_bytes[0] &= 0x3f;
var value = new BigInteger(mantissa_bytes, 256);
if (value.equals(BigInteger.ZERO) && !is_zero ) {
throw new Error('Invalid zero representation');
}
amount._value = value;
amount._offset = offset;
amount._currency = currency;
amount._issuer = issuer;
amount._is_native = false;
} else {
//native
var integer_bytes = value_bytes.slice();
integer_bytes[0] &= 0x3f;
amount._value = new BigInteger(integer_bytes, 256);
amount._is_native = true;
}
amount._is_negative = !is_zero && !(value_bytes[0] & 0x40);
return amount;
}
});
STAmount.id = 6;
var STVL = exports.VariableLength = exports.VL = new SerializedType({
serialize: function (so, val) {
if (typeof val === 'string') {
serialize_hex(so, val);
} else {
throw new Error('Unknown datatype.');
}
},
parse: function (so) {
var len = this.parse_varint(so);
return convert_bytes_to_hex(so.read(len));
}
});
STVL.id = 7;
var STAccount = exports.Account = new SerializedType({
serialize: function (so, val) {
var account = UInt160.from_json(val);
if (!account.is_valid()) {
throw new Error('Invalid account!');
}
serialize_hex(so, account.to_hex());
},
parse: function (so) {
var len = this.parse_varint(so);
if (len !== 20) {
throw new Error('Non-standard-length account ID');
}
var result = UInt160.from_bytes(so.read(len));
result.set_version(Base.VER_ACCOUNT_ID);
//console.log('PARSED 160:', result.to_json());
if (false && !result.is_valid()) {
throw new Error('Invalid Account');
}
return result;
}
});
STAccount.id = 8;
var STPathSet = exports.PathSet = new SerializedType({
typeBoundary: 0xff,
typeEnd: 0x00,
typeAccount: 0x01,
typeCurrency: 0x10,
typeIssuer: 0x20,
serialize: function (so, val) {
for (var i=0, l=val.length; i<l; i++) {
// Boundary
if (i) {
STInt8.serialize(so, this.typeBoundary);
}
for (var j=0, l2=val[i].length; j<l2; j++) {
var entry = val[i][j];
//if (entry.hasOwnProperty('_value')) {entry = entry._value;}
var type = 0;
if (entry.account) type |= this.typeAccount;
if (entry.currency) type |= this.typeCurrency;
if (entry.issuer) type |= this.typeIssuer;
STInt8.serialize(so, type);
if (entry.account) {
so.append(UInt160.from_json(entry.account).to_bytes());
}
if (entry.currency) {
var currency = Currency.from_json(entry.currency, entry.non_native);
STCurrency.serialize(so, currency);
}
if (entry.issuer) {
so.append(UInt160.from_json(entry.issuer).to_bytes());
}
}
}
STInt8.serialize(so, this.typeEnd);
},
parse: function (so) {
// should return a list of lists:
/*
[
[entry, entry],
[entry, entry, entry],
[entry],
[]
]
each entry has one or more of the following attributes: amount, currency, issuer.
*/
var path_list = [];
var current_path = [];
var tag_byte;
while ((tag_byte = so.read(1)[0]) !== this.typeEnd) {
//TODO: try/catch this loop, and catch when we run out of data without reaching the end of the data structure.
//Now determine: is this an end, boundary, or entry-begin-tag?
//console.log('Tag byte:', tag_byte);
if (tag_byte === this.typeBoundary) {
//console.log('Boundary');
if (current_path) { //close the current path, if there is one,
path_list.push(current_path);
}
current_path = []; //and start a new one.
} else {
//It's an entry-begin tag.
//console.log('It's an entry-begin tag.');
var entry = {};
if (tag_byte & this.typeAccount) {
//console.log('entry.account');
/*var bta = so.read(20);
console.log('BTA:', bta);*/
entry.account = STHash160.parse(so);
entry.account.set_version(Base.VER_ACCOUNT_ID);
}
if (tag_byte & this.typeCurrency) {
//console.log('entry.currency');
entry.currency = STCurrency.parse(so);
if (entry.currency.to_json() === "XRP" &&
!entry.currency.is_native()) {
entry.non_native = true;
}
}
if (tag_byte & this.typeIssuer) {
//console.log('entry.issuer');
entry.issuer = STHash160.parse(so);
// Enable and set correct type of base-58 encoding
entry.issuer.set_version(Base.VER_ACCOUNT_ID);
//console.log('DONE WITH ISSUER!');
}
if (entry.account || entry.currency || entry.issuer) {
current_path.push(entry);
} else {
throw new Error('Invalid path entry'); //It must have at least something in it.
}
}
}
if (current_path) {
//close the current path, if there is one,
path_list.push(current_path);
}
return path_list;
}
});
STPathSet.id = 18;
var STVector256 = exports.Vector256 = new SerializedType({
serialize: function (so, val) { //Assume val is an array of STHash256 objects.
var length_as_varint = SerializedType.serialize_varint(so, val.length * 32);
for (var i=0, l=val.length; i<l; i++) {
STHash256.serialize(so, val[i]);
}
},
parse: function (so) {
var length = this.parse_varint(so);
var output = [];
// length is number of bytes not number of Hash256
for (var i=0; i<length / 32; i++) {
output.push(STHash256.parse(so));
}
return output;
}
});
STVector256.id = 19;
exports.serialize = exports.serialize_whatever = serialize;
function serialize(so, field_name, value) {
//so: a byte-stream to serialize into.
//field_name: a string for the field name ('LedgerEntryType' etc.)
//value: the value of that field.
var field_coordinates = binformat.fieldsInverseMap[field_name];
var type_bits = field_coordinates[0];
var field_bits = field_coordinates[1];
var tag_byte = (type_bits < 16 ? type_bits << 4 : 0) | (field_bits < 16 ? field_bits : 0);
if (field_name === "LedgerEntryType" && "string" === typeof value) {
value = binformat.ledger[value][0];
}
if (field_name === "TransactionResult" && "string" === typeof value) {
value = binformat.ter[value];
}
STInt8.serialize(so, tag_byte);
if (type_bits >= 16) {
STInt8.serialize(so, type_bits);
}
if (field_bits >= 16) {
STInt8.serialize(so, field_bits);
}
// Get the serializer class (ST...) for a field based on the type bits.
var serialized_object_type = exports[binformat.types[type_bits]];
//do something with val[keys] and val[keys[i]];
serialized_object_type.serialize(so, value);
}
//Take the serialized object, figure out what type/field it is, and return the parsing of that.
exports.parse = exports.parse_whatever = parse;
function parse(so) {
var tag_byte = so.read(1)[0];
var type_bits = tag_byte >> 4;
if (type_bits === 0) {
type_bits = so.read(1)[0];
}
// Get the parser class (ST...) for a field based on the type bits.
var type = exports[binformat.types[type_bits]];
assert(type, 'Unknown type - header byte is 0x' + tag_byte.toString(16));
var field_bits = tag_byte & 0x0f;
var field_name = (field_bits === 0)
? field_name = binformat.fields[type_bits][so.read(1)[0]]
: field_name = binformat.fields[type_bits][field_bits];
assert(field_name, 'Unknown field - header byte is 0x' + tag_byte.toString(16));
return [ field_name, type.parse(so) ]; //key, value
};
function sort_fields(keys) {
function sort_field_compare(a, b) {
var a_field_coordinates = binformat.fieldsInverseMap[a];
var a_type_bits = a_field_coordinates[0];
var a_field_bits = a_field_coordinates[1];
var b_field_coordinates = binformat.fieldsInverseMap[b];
var b_type_bits = b_field_coordinates[0];
var b_field_bits = b_field_coordinates[1];
// Sort by type id first, then by field id
return a_type_bits !== b_type_bits ? a_type_bits - b_type_bits : a_field_bits - b_field_bits;
};
return keys.sort(sort_field_compare);
}
var STObject = exports.Object = new SerializedType({
serialize: function (so, val, no_marker) {
var keys = Object.keys(val);
// Ignore lowercase field names - they're non-serializable fields by
// convention.
keys = keys.filter(function (key) {
return key[0] !== key[0].toLowerCase();
});
keys.forEach(function (key) {
if ("undefined" === typeof binformat.fieldsInverseMap[key]) {
throw new Error("JSON contains unknown field: '" + key + "'");
}
});
// Sort fields
keys = sort_fields(keys);
for (var i=0; i<keys.length; i++) {
serialize(so, keys[i], val[keys[i]]);
}
if (!no_marker) STInt8.serialize(so, 0xe1); //Object ending marker
},
parse: function (so) {
var output = {};
while (so.peek(1)[0] !== 0xe1) {
var keyval = parse(so);
output[keyval[0]] = keyval[1];
}
so.read(1);
return output;
}
});
STObject.id = 14;
var STArray = exports.Array = new SerializedType({
serialize: function (so, val) {
for (var i=0, l=val.length; i<l; i++) {
var keys = Object.keys(val[i]);
if (keys.length !== 1) {
throw Error('Cannot serialize an array containing non-single-key objects');
}
var field_name = keys[0];
var value = val[i][field_name];
serialize(so, field_name, value);
}
STInt8.serialize(so, 0xf1); //Array ending marker
},
parse: function (so) {
var output = [ ];
while (so.peek(1)[0] !== 0xf1) {
var keyval = parse(so);
var obj = { };
obj[keyval[0]] = keyval[1];
output.push(obj);
}
so.read(1);
return output;
}
});
STArray.id = 15;

584
src/js/ripple/server.js Normal file
View File

@@ -0,0 +1,584 @@
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var Transaction = require('./transaction').Transaction;
var Amount = require('./amount').Amount;
var utils = require('./utils');
var log = require('./log').internal.sub('server');
/**
* @constructor Server
* @param {Remote} Reference to a Remote object
* @param {Object} Options
*
* host: String
* port: String or Number
* secure: Boolean
*/
function Server(remote, opts) {
EventEmitter.call(this);
if (typeof opts !== 'object') {
throw new TypeError('Server configuration is not an Object');
}
if (!opts.host) opts.host = opts.websocket_ip;
if (!opts.port) opts.port = opts.websocket_port;
if (!opts.secure) opts.secure = opts.websocket_ssl;
if (typeof opts.secure === 'undefined') {
opts.secure = false;
}
var domainRE = /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/;
if (!domainRE.test(opts.host)) {
throw new Error('Server host is malformed, use "host" and "port" server configuration');
}
if (typeof opts.port !== 'number') {
throw new TypeError('Server configuration "port" is not a Number');
}
if (typeof opts.secure !== 'boolean') {
throw new TypeError('Server "secure" configuration is not a Boolean');
}
var self = this;
this._remote = remote;
this._opts = opts;
this._host = opts.host;
this._port = opts.port;
this._secure = opts.secure;
this._ws = void(0);
this._connected = false;
this._shouldConnect = false;
this._state = 'offline';
this._id = 0;
this._retry = 0;
this._requests = { };
this._load_base = 256;
this._load_factor = 256;
this._fee_ref = 10;
this._fee_base = 10;
this._reserve_base = void(0);
this._reserve_inc = void(0);
this._fee_cushion = this._remote.fee_cushion;
this._opts.url = (opts.secure ? 'wss://' : 'ws://') + opts.host + ':' + opts.port;
this.on('message', function(message) {
self._handleMessage(message);
});
this.on('response_subscribe', function(message) {
self._handleResponseSubscribe(message);
});
function checkServerActivity() {
if (isNaN(self._lastLedgerClose)) return;
var delta = (Date.now() - self._lastLedgerClose);
if (delta > (1000 * 20)) {
self.reconnect();
}
};
function setActivityInterval() {
self._activityInterval = setInterval(checkServerActivity, 1000);
};
this.on('disconnect', function onDisconnect() {
clearInterval(self._activityInterval);
//self.once('ledger_closed', setActivityInterval);
});
this.once('ledger_closed', function() {
//setActiviyInterval();
});
};
util.inherits(Server, EventEmitter);
/**
* Server states that we will treat as the server being online.
*
* Our requirements are that the server can process transactions and notify
* us of changes.
*/
Server.onlineStates = [
'syncing',
'tracking',
'proposing',
'validating',
'full'
];
/**
* Set server state
*
* @param {String} state
* @api private
*/
Server.prototype._setState = function(state) {
if (state !== this._state) {
this._remote.trace && log.info('set_state:', state);
this._state = state;
this.emit('state', state);
switch (state) {
case 'online':
this._connected = true;
this.emit('connect');
break;
case 'offline':
this._connected = false;
this.emit('disconnect');
break;
}
}
};
/**
* Get the remote address for a server.
* Incompatible with ripple-lib client build
*/
Server.prototype._remoteAddress = function() {
try { var address = this._ws._socket.remoteAddress; } catch (e) { }
return address;
};
/** This is the final interface between client code and a socket connection to a
* `rippled` server. As such, this is a decent hook point to allow a WebSocket
* interface conforming object to be used as a basis to mock rippled. This
* avoids the need to bind a websocket server to a port and allows a more
* synchronous style of code to represent a client <-> server message sequence.
* We can also use this to log a message sequence to a buffer.
*
* @api private
*/
Server.websocketConstructor = function() {
// We require this late, because websocket shims may be loaded after
// ripple-lib in the browser
return require('ws');
};
/**
* Disconnect from rippled WebSocket server
*
* @api public
*/
Server.prototype.disconnect = function() {
this._shouldConnect = false;
this._setState('offline');
if (this._ws) this._ws.close();
};
/**
* Reconnect to rippled WebSocket server
*
* @api public
*/
Server.prototype.reconnect = function() {
if (this._ws) {
this.once('disconnect', this.connect.bind(this));
this.disconnect();
}
};
/**
* Connect to rippled WebSocket server and subscribe to events that are
* internally requisite. Automatically retry connections with a gradual
* back-off
*
* @api public
*/
Server.prototype.connect = function() {
var self = this;
// We don't connect if we believe we're already connected. This means we have
// 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,
// we will automatically reconnect.
if (this._connected) return;
this._remote.trace && log.info('connect:', this._opts.url);
// Ensure any existing socket is given the command to close first.
if (this._ws) this._ws.close();
var WebSocket = Server.websocketConstructor();
if (!WebSocket) {
throw new Error('No websocket support detected!');
}
var ws = this._ws = new WebSocket(this._opts.url);
this._shouldConnect = true;
self.emit('connecting');
ws.onmessage = function onMessage(msg) {
self.emit('message', msg.data);
};
ws.onopen = function onOpen() {
// If we are no longer the active socket, simply ignore any event
if (ws === self._ws) {
self.emit('socket_open');
// Subscribe to events
self.request(self._remote._serverPrepareSubscribe());
}
};
ws.onerror = function onError(e) {
// If we are no longer the active socket, simply ignore any event
if (ws === self._ws) {
self.emit('socket_error');
self._remote.trace && log.info('onerror:', self._opts.url, e.data || e);
// Most connection errors for WebSockets are conveyed as 'close' events with
// code 1006. This is done for security purposes and therefore unlikely to
// ever change.
// This means that this handler is hardly ever called in practice. If it is,
// it probably means the server's WebSocket implementation is corrupt, or
// the connection is somehow producing corrupt data.
// Most WebSocket applications simply log and ignore this error. Once we
// support for multiple servers, we may consider doing something like
// lowering this server's quality score.
// However, in Node.js this event may be triggered instead of the close
// event, so we need to handle it.
self._handleClose();
}
};
// Failure to open.
ws.onclose = function onClose() {
// If we are no longer the active socket, simply ignore any event
if (ws === self._ws) {
self._remote.trace && log.info('onclose:', self._opts.url, ws.readyState);
self._handleClose();
}
};
};
/**
* Retry connection to rippled server
*
* @api private
*/
Server.prototype._retryConnect = function() {
var self = this;
this._retry += 1;
var retryTimeout = (this._retry < 40)
? (1000 / 20) // First, for 2 seconds: 20 times per second
: (this._retry < 40 + 60)
? (1000) // Then, for 1 minute: once per second
: (this._retry < 40 + 60 + 60)
? (10 * 1000) // Then, for 10 minutes: once every 10 seconds
: (30 * 1000); // Then: once every 30 seconds
function connectionRetry() {
if (self._shouldConnect) {
self._remote.trace && log.info('retry', self._opts.url);
self.connect();
}
};
this._retryTimer = setTimeout(connectionRetry, retryTimeout);
};
/**
* Handle connection closes
*
* @api private
*/
Server.prototype._handleClose = function() {
var self = this;
var ws = this._ws;
this.emit('socket_close');
this._setState('offline');
// Prevent additional events from this socket
ws.onopen = ws.onerror = ws.onclose = ws.onmessage = function noOp() {};
if (self._shouldConnect) {
this._retryConnect();
}
};
/**
* Handle incoming messages from rippled WebSocket server
*
* @param {JSON-parseable} message
* @api private
*/
Server.prototype._handleMessage = function(message) {
var self = this;
try { message = JSON.parse(message); } catch(e) { }
if (!Server.isValidMessage(message)) return;
switch (message.type) {
case 'ledgerClosed':
this._lastLedgerClose = Date.now();
this.emit('ledger_closed', message);
break;
case 'serverStatus':
// This message is only received when online.
// As we are connected, it is the definitive final state.
this._setState(~(Server.onlineStates.indexOf(message.server_status)) ? 'online' : 'offline');
if (Server.isLoadStatus(message)) {
self.emit('load', message, self);
self._remote.emit('load', message, self);
var loadChanged = ((message.load_base !== self._load_base) ||
(message.load_factor !== self._load_factor));
if (loadChanged) {
self._load_base = message.load_base;
self._load_factor = message.load_factor;
self.emit('load_changed', message, self);
self._remote.emit('load_changed', message, self);
}
}
break;
case 'response':
// A response to a request.
var request = self._requests[message.id];
delete self._requests[message.id];
if (!request) {
this._remote.trace && log.info('UNEXPECTED:', self._opts.url, message);
} else if (message.status === 'success') {
this._remote.trace && log.info('response:', self._opts.url, message);
request.emit('success', message.result);
[ self, self._remote ].forEach(function(emitter) {
emitter.emit('response_' + request.message.command, message.result, request, message);
});
} else if (message.error) {
this._remote.trace && log.info('error:', self._opts.url, message);
request.emit('error', {
error : 'remoteError',
error_message : 'Remote reported an error.',
remote : message
});
}
break;
case 'path_find':
this._remote.trace && log.info('path_find:', self._opts.url, message);
break;
}
};
/**
* Check that received message from rippled is valid
*
* @api private
*/
Server.isValidMessage = function(message) {
return (typeof message === 'object')
&& (typeof message.type === 'string');
};
/**
* Check that received serverStatus message contains
* load status information
*
* @api private
*/
Server.isLoadStatus = function(message) {
return (typeof message.load_base === 'number')
&& (typeof message.load_factor === 'number');
};
/**
* Handle subscription response messages. Subscription response
* messages indicate that a connection to the server is ready
*
* @api private
*/
Server.prototype._handleResponseSubscribe = function(message) {
if (~(Server.onlineStates.indexOf(message.server_status))) {
this._setState('online');
}
if (Server.isLoadStatus(message)) {
this._load_base = message.load_base || 256;
this._load_factor = message.load_factor || 256;
this._fee_ref = message.fee_ref;
this._fee_base = message.fee_base;
this._reserve_base = message.reserve_base;
this._reserve_inc = message.reserve_inc;
}
};
/**
* Send JSON message to rippled WebSocket server
*
* @param {JSON-Stringifiable} message
* @api private
*/
Server.prototype.sendMessage = function(message) {
if (this._ws) {
this._remote.trace && log.info('request:', this._opts.url, message);
this._ws.send(JSON.stringify(message));
}
};
/**
* Submit a Request object.
*
* Requests are indexed by message ID, which is repeated
* in the response from rippled WebSocket server
*
* @param {Request} request
* @api private
*/
Server.prototype.request = function(request) {
var self = this;
// Only bother if we are still connected.
if (!this._ws) {
this._remote.trace && log.info('request: DROPPING:', self._opts.url, request.message);
return;
}
request.server = this;
request.message.id = this._id;
this._requests[request.message.id] = request;
// Advance message ID
this._id++;
if (this._isConnected(request)) {
this.sendMessage(request.message);
} else {
// XXX There are many ways to make this smarter.
function serverReconnected() {
self.sendMessage(request.message);
}
this.once('connect', serverReconnected);
}
};
Server.prototype._isConnected = function(request) {
var isSubscribeRequest = request
&& request.message.command === 'subscribe'
&& this._ws.readyState === 1;
return this._connected || (this._ws && isSubscribeRequest);
};
/**
* Calculate transaction fee
*
* @param {Transaction|Number} Fee units for a provided transaction
* @return {Number} Final fee in XRP for specified number of fee units
* @api private
*/
Server.prototype.computeFee = function(transaction) {
var units;
if (transaction instanceof Transaction) {
units = transaction.feeUnits();
} else if (typeof transaction === 'number') {
units = transaction;
} else {
throw new Error('Invalid argument');
}
return this.feeTx(units).to_json();
};
/**
* Calculate a transaction fee for a number of tx fee units.
*
* This takes into account the last known network and local load fees.
*
* @param {Number} Fee units for a provided transaction
* @return {Amount} Final fee in XRP for specified number of fee units.
*/
Server.prototype.feeTx = function(units) {
var fee_unit = this.feeTxUnit();
return Amount.from_json(String(Math.ceil(units * fee_unit)));
};
/**
* Get the current recommended transaction fee unit.
*
* Multiply this value with the number of fee units in order to calculate the
* recommended fee for the transaction you are trying to submit.
*
* @return {Number} Recommended amount for one fee unit as float.
*/
Server.prototype.feeTxUnit = function() {
var fee_unit = this._fee_base / this._fee_ref;
// Apply load fees
fee_unit *= this._load_factor / this._load_base;
// Apply fee cushion (a safety margin in case fees rise since we were last updated)
fee_unit *= this._fee_cushion;
return fee_unit;
};
/**
* Get the current recommended reserve base.
*
* Returns the base reserve with load fees and safety margin applied.
*/
Server.prototype.reserve = function(owner_count) {
var reserve_base = Amount.from_json(String(this._reserve_base));
var reserve_inc = Amount.from_json(String(this._reserve_inc));
var owner_count = owner_count || 0;
if (owner_count < 0) {
throw new Error('Owner count must not be negative.');
}
return reserve_base.add(reserve_inc.product_human(owner_count));
};
exports.Server = Server;
// vim:sw=2:sts=2:ts=8:et

173
src/js/ripple/shamap.js Normal file
View File

@@ -0,0 +1,173 @@
var util = require('util');
var sjcl = require('./utils').sjcl;
var stypes = require('./serializedtypes');
var hashprefixes = require('./hashprefixes');
var UInt256 = require('./uint256').UInt256;
var SerializedObject = require('./serializedobject').SerializedObject;
function SHAMap() {
this.root = new SHAMapTreeNodeInner();
};
SHAMap.prototype.add_item = function (tag, node, type) {
var node = new SHAMapTreeNodeLeaf(tag, node, type);
this.root.add_item(tag, node);
};
SHAMap.prototype.hash = function () {
return this.root.hash();
};
/**
* Abstract class representing a node in a SHAMap tree.
*
* Can be either SHAMapTreeNodeInner or SHAMapTreeNodeLeaf.
*/
function SHAMapTreeNode() {
};
SHAMapTreeNode.TYPE_INNER = 1;
SHAMapTreeNode.TYPE_TRANSACTION_NM = 2;
SHAMapTreeNode.TYPE_TRANSACTION_MD = 3;
SHAMapTreeNode.TYPE_ACCOUNT_STATE = 4;
SHAMapTreeNode.prototype.add_item = function (tag_segment, node) {
throw new Error("Called unimplemented virtual method SHAMapTreeNode#add_item.");
};
SHAMapTreeNode.prototype.hash = function () {
throw new Error("Called unimplemented virtual method SHAMapTreeNode#hash.");
};
/**
* Inner (non-leaf) node in a SHAMap tree.
*/
function SHAMapTreeNodeInner() {
SHAMapTreeNode.call(this);
this.leaves = {};
this.type = SHAMapTreeNode.INNER;
this.empty = true;
}
util.inherits(SHAMapTreeNodeInner, SHAMapTreeNode);
SHAMapTreeNodeInner.prototype.add_item = function (tag_segment, node) {
var current_node = this.get_node(tag_segment[0]);
if (current_node) {
// A node already exists in this slot
if (current_node instanceof SHAMapTreeNodeInner) {
// There is an inner node, so we need to go deeper
current_node.add_item(tag_segment.slice(1), node);
} else if (current_node.get_segment() === tag_segment) {
// Collision
throw new Error("Tried to add a node to a SHAMap that was already in there.");
} else {
// Turn it into an inner node
var new_inner_node = new SHAMapTreeNodeInner();
// Move the existing leaf node down one level
current_node.set_segment(current_node.get_segment().slice(1));
new_inner_node.set_node(current_node.get_segment()[0], current_node);
// Add the new node next to it
new_inner_node.add_item(tag_segment.slice(1), node);
// And place the newly created inner node in the slot
this.set_node(tag_segment[0], new_inner_node);
}
} else {
// Neat, we have a nice open spot for the new node
node.set_segment(tag_segment);
this.set_node(tag_segment[0], node);
}
};
/**
* Overwrite the node that is currently in a given slot.
*/
SHAMapTreeNodeInner.prototype.set_node = function (slot, node) {
this.leaves[slot] = node;
this.empty = false;
};
SHAMapTreeNodeInner.prototype.get_node = function (slot) {
return this.leaves[slot];
};
SHAMapTreeNodeInner.prototype.hash = function () {
if (this.empty) {
return UInt256.from_hex(UInt256.HEX_ZERO);
}
var hash_buffer = new SerializedObject();
var buffer = [];
for (var i = 0; i < 16; i++) {
var leafHash = UInt256.from_hex(UInt256.HEX_ZERO);
var slot = i.toString(16).toUpperCase();
if ("object" === typeof this.leaves[slot]) {
leafHash = this.leaves[slot].hash();
}
hash_buffer.append(leafHash.to_bytes());
}
var hash = hash_buffer.hash(hashprefixes.HASH_INNER_NODE);
return UInt256.from_bits(hash);
};
/**
* Leaf node in a SHAMap tree.
*/
function SHAMapTreeNodeLeaf(tag, node, type) {
SHAMapTreeNode.call(this);
if ("string" === typeof tag) {
tag = UInt256.from_hex(tag);
} else if (tag instanceof UInt256) {
// Type is already the right one
} else {
throw new Error("Tag is unexpected type.");
}
this.tag = tag;
this.tag_segment = null;
this.type = type;
this.node = node;
}
util.inherits(SHAMapTreeNodeLeaf, SHAMapTreeNode);
SHAMapTreeNodeLeaf.prototype.get_segment = function (segment) {
return this.tag_segment;
};
SHAMapTreeNodeLeaf.prototype.set_segment = function (segment) {
this.tag_segment = segment;
};
SHAMapTreeNodeLeaf.prototype.hash = function () {
var buffer = new SerializedObject();
switch (this.type) {
case SHAMapTreeNode.TYPE_TRANSACTION_NM:
return this.tag;
case SHAMapTreeNode.TYPE_TRANSACTION_MD:
buffer.append(this.node);
buffer.append(this.tag.to_bytes());
return buffer.hash(hashprefixes.HASH_TX_NODE);
default:
throw new Error("Tried to hash a SHAMap node of unknown type.");
}
};
exports.SHAMap = SHAMap;
exports.SHAMapTreeNode = SHAMapTreeNode;
exports.SHAMapTreeNodeInner = SHAMapTreeNodeInner;
exports.SHAMapTreeNodeLeaf = SHAMapTreeNodeLeaf;

View File

@@ -0,0 +1,827 @@
// Transactions
//
// Construction:
// remote.transaction() // Build a transaction object.
// .offer_create(...) // Set major parameters.
// .set_flags() // Set optional parameters.
// .on() // Register for events.
// .submit(); // Send to network.
//
// Events:
// 'success' : Transaction submitted without error.
// 'error' : Error submitting transaction.
// 'proposed' : Advisory proposed status transaction.
// - A client should expect 0 to multiple results.
// - Might not get back. The remote might just forward the transaction.
// - A success could be reverted in final.
// - local error: other remotes might like it.
// - malformed error: local server thought it was malformed.
// - The client should only trust this when talking to a trusted server.
// 'final' : Final status of transaction.
// - Only expect a final from dishonest servers after a tesSUCCESS or ter*.
// 'lost' : Gave up looking for on ledger_closed.
// 'pending' : Transaction was not found on ledger_closed.
// 'state' : Follow the state of a transaction.
// 'client_submitted' - Sent to remote
// |- 'remoteError' - Remote rejected transaction.
// \- 'client_proposed' - Remote provisionally accepted transaction.
// |- 'client_missing' - Transaction has not appeared in ledger as expected.
// | |\- 'client_lost' - No longer monitoring missing transaction.
// |/
// |- 'tesSUCCESS' - Transaction in ledger as expected.
// |- 'ter...' - Transaction failed.
// \- 'tec...' - Transaction claimed fee only.
//
// Notes:
// - All transactions including those with local and malformed errors may be
// forwarded anyway.
// - A malicous server can:
// - give any proposed result.
// - it may declare something correct as incorrect or something incorrect as correct.
// - it may not communicate with the rest of the network.
// - may or may not forward.
//
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var sjcl = require('./utils').sjcl;
var Amount = require('./amount').Amount;
var Currency = require('./amount').Currency;
var UInt160 = require('./amount').UInt160;
var Seed = require('./seed').Seed;
var SerializedObject = require('./serializedobject').SerializedObject;
var RippleError = require('./rippleerror').RippleError;
var hashprefixes = require('./hashprefixes');
var config = require('./config');
function Transaction(remote) {
EventEmitter.call(this);
var self = this;
this.remote = remote;
// Transaction data
this.tx_json = { Flags: 0 };
this._secret = void(0);
this._build_path = false;
this.state = 'unsubmitted';
this.finalized = false;
this.previousSigningHash = void(0);
// Index at which transaction was submitted
this.submitIndex = void(0);
// We aren't clever enough to eschew preventative measures so we keep an array
// of all submitted transactionIDs (which can change due to load_factor
// effecting the Fee amount). This should be populated with a transactionID
// any time it goes on the network
this.submittedIDs = [ ]
function finalize(message) {
if (self.result) {
self.result.ledger_index = message.ledger_index;
self.result.ledger_hash = message.ledger_hash;
} else {
self.result = message;
self.result.tx_json = self.tx_json;
}
self.emit('cleanup', message);
};
this.once('success', function(message) {
self.finalized = true;
self.setState('validated');
finalize(message);
});
this.once('error', function(message) {
self.finalized = true;
self.setState('failed');
finalize(message);
});
this.once('submitted', function() {
self.setState('submitted');
});
this.once('proposed', function() {
self.setState('pending');
});
};
util.inherits(Transaction, EventEmitter);
// XXX This needs to be determined from the network.
Transaction.fee_units = {
default: 10
};
Transaction.flags = {
AccountSet: {
RequireDestTag: 0x00010000,
OptionalDestTag: 0x00020000,
RequireAuth: 0x00040000,
OptionalAuth: 0x00080000,
DisallowXRP: 0x00100000,
AllowXRP: 0x00200000
},
TrustSet: {
SetAuth: 0x00010000,
NoRipple: 0x00020000,
ClearNoRipple: 0x00040000
},
OfferCreate: {
Passive: 0x00010000,
ImmediateOrCancel: 0x00020000,
FillOrKill: 0x00040000,
Sell: 0x00080000
},
Payment: {
NoRippleDirect: 0x00010000,
PartialPayment: 0x00020000,
LimitQuality: 0x00040000
}
};
Transaction.formats = require('./binformat').tx;
Transaction.prototype.consts = {
telLOCAL_ERROR: -399,
temMALFORMED: -299,
tefFAILURE: -199,
terRETRY: -99,
tesSUCCESS: 0,
tecCLAIMED: 100
};
Transaction.from_json = function(j) {
return (new Transaction()).parseJson(j);
};
Transaction.prototype.parseJson = function(v) {
this.tx_json = v;
return this;
};
Transaction.prototype.isTelLocal = function(ter) {
return ter >= this.consts.telLOCAL_ERROR && ter < this.consts.temMALFORMED;
};
Transaction.prototype.isTemMalformed = function(ter) {
return ter >= this.consts.temMALFORMED && ter < this.consts.tefFAILURE;
};
Transaction.prototype.isTefFailure = function(ter) {
return ter >= this.consts.tefFAILURE && ter < this.consts.terRETRY;
};
Transaction.prototype.isTerRetry = function(ter) {
return ter >= this.consts.terRETRY && ter < this.consts.tesSUCCESS;
};
Transaction.prototype.isTepSuccess = function(ter) {
return ter >= this.consts.tesSUCCESS;
};
Transaction.prototype.isTecClaimed = function(ter) {
return ter >= this.consts.tecCLAIMED;
};
Transaction.prototype.isRejected = function(ter) {
return this.isTelLocal(ter) || this.isTemMalformed(ter) || this.isTefFailure(ter);
};
Transaction.prototype.setState = function(state) {
if (this.state !== state) {
this.state = state;
this.emit('state', state);
this.emit('save');
}
};
Transaction.prototype._accountSecret = function(account) {
return this.remote.secrets[account];
};
/**
* Returns the number of fee units this transaction will cost.
*
* Each Ripple transaction based on its type and makeup costs a certain number
* of fee units. The fee units are calculated on a per-server basis based on the
* current load on both the network and the server.
*
* @see https://ripple.com/wiki/Transaction_Fee
*
* @return {Number} Number of fee units for this transaction.
*/
Transaction.prototype.getFee =
Transaction.prototype.feeUnits = function() {
return Transaction.fee_units['default'];
};
/**
* Get the server whose fee is currently the lowest
*/
Transaction.prototype._getServer = function() {
var self = this;
var servers = this.remote._servers;
var fee = Infinity;
var result;
for (var i=0; i<servers.length; i++) {
var server = servers[i];
if (!server._connected) continue;
var n = server.computeFee(this);
if (n < fee) {
result = server;
fee = n;
}
}
return result;
};
/**
* Attempts to complete the transaction for submission.
*
* This function seeks to fill out certain fields, such as Fee and
* SigningPubKey, which can be determined by the library based on network
* information and other fields.
*/
Transaction.prototype.complete = function() {
// Try to auto-fill the secret
if (!this._secret && !(this._secret = this._account_secret(this.tx_json.Account))) {
return this.emit('error', new RippleError('tejSecretUnknown', 'Missing secret'));
}
// If the Fee hasn't been set, one needs to be computed by
// an assigned server
if (this.remote && typeof this.tx_json.Fee === 'undefined') {
if (this.remote.local_fee || !this.remote.trusted) {
this._server = this._getServer();
this.tx_json.Fee = this._server.computeFee(this);
}
}
if (typeof this.tx_json.SigningPubKey === 'undefined') {
var seed = Seed.from_json(this._secret);
var key = seed.get_key(this.tx_json.Account);
this.tx_json.SigningPubKey = key.to_hex_pub();
}
return this.tx_json;
};
Transaction.prototype.serialize = function() {
return SerializedObject.from_json(this.tx_json);
};
Transaction.prototype.signingHash = function() {
return this.hash(config.testnet ? 'HASH_TX_SIGN_TESTNET' : 'HASH_TX_SIGN');
};
Transaction.prototype.hash = function(prefix, as_uint256) {
if (typeof prefix === 'string') {
if (typeof hashprefixes[prefix] === 'undefined') {
throw new Error('Unknown hashing prefix requested.');
}
prefix = hashprefixes[prefix];
} else if (!prefix) {
prefix = hashprefixes['HASH_TX_ID'];
}
var hash = SerializedObject.from_json(this.tx_json).hash(prefix);
return as_uint256 ? hash : hash.to_hex();
};
Transaction.prototype.sign = function() {
var seed = Seed.from_json(this._secret);
var prev_sig = this.tx_json.TxnSignature;
delete this.tx_json.TxnSignature;
var hash = this.signingHash();
// If the hash is the same, we can re-use the previous signature
if (prev_sig && hash === this.previousSigningHash) {
this.tx_json.TxnSignature = prev_sig;
return this;
}
var key = seed.get_key(this.tx_json.Account);
var sig = key.sign(hash, 0);
var hex = sjcl.codec.hex.fromBits(sig).toUpperCase();
this.tx_json.TxnSignature = hex;
this.previousSigningHash = hash;
return this;
};
/**
* Add a ID to list of submitted IDs for this transaction
*/
Transaction.prototype.addId = function(hash) {
if (this.submittedIDs.indexOf(hash) === -1) {
this.submittedIDs.unshift(hash);
this.emit('signed', hash);
this.emit('save');
}
};
/**
* Find ID within list of submitted IDs for this transaction
*/
Transaction.prototype.findId = function(cache) {
var result;
for (var i=0; i<this.submittedIDs.length; i++) {
var hash = this.submittedIDs[i];
if (result = cache[hash]) break;
}
return result;
};
//
// Set options for Transactions
//
// --> build: true, to have server blindly construct a path.
//
// "blindly" because the sender has no idea of the actual cost except that is must be less than send max.
Transaction.prototype.buildPath = function(build) {
this._build_path = build;
return this;
};
// tag should be undefined or a 32 bit integer.
// YYY Add range checking for tag.
Transaction.prototype.destinationTag = function(tag) {
if (tag !== void(0)) {
this.tx_json.DestinationTag = tag;
}
return this;
};
Transaction.prototype.invoiceID = function(id) {
if (typeof id === 'string') {
while (id.length < 64) id += '0';
this.tx_json.InvoiceID = id;
}
return this;
};
Transaction.prototype.clientID = function(id) {
if (typeof id === 'string') {
this._clientID = id;
}
return this;
};
Transaction.prototype.lastLedger = function(sequence) {
if (typeof sequence === 'number') {
this._setLastLedger = true;
this.tx_json.LastLedgerSequence = sequence;
}
return this;
};
Transaction._pathRewrite = function(path) {
return path.map(function(node) {
var newNode = { };
if (node.hasOwnProperty('account')) {
newNode.account = UInt160.json_rewrite(node.account);
}
if (node.hasOwnProperty('issuer')) {
newNode.issuer = UInt160.json_rewrite(node.issuer);
}
if (node.hasOwnProperty('currency')) {
newNode.currency = Currency.json_rewrite(node.currency);
}
if (node.hasOwnProperty('type_hex')) {
newNode.type_hex = node.type_hex;
}
return newNode
});
};
Transaction.prototype.pathAdd = function(path) {
if (Array.isArray(path)) {
this.tx_json.Paths = this.tx_json.Paths || [];
this.tx_json.Paths.push(Transaction._pathRewrite(path));
}
return this;
};
// --> paths: undefined or array of path
// // A path is an array of objects containing some combination of: account, currency, issuer
Transaction.prototype.paths = function(paths) {
if (Array.isArray(paths)) {
for (var i=0, l=paths.length; i<l; i++) {
this.pathAdd(paths[i]);
}
}
return this;
};
// If the secret is in the config object, it does not need to be provided.
Transaction.prototype.secret = function(secret) {
this._secret = secret;
return this;
};
Transaction.prototype.sendMax = function(send_max) {
if (send_max) {
this.tx_json.SendMax = Amount.json_rewrite(send_max);
}
return this;
};
// tag should be undefined or a 32 bit integer.
// YYY Add range checking for tag.
Transaction.prototype.sourceTag = function(tag) {
if (tag) {
this.tx_json.SourceTag = tag;
}
return this;
};
// --> rate: In billionths.
Transaction.prototype.transferRate = function(rate) {
this.tx_json.TransferRate = Number(rate);
if (this.tx_json.TransferRate < 1e9) {
throw new Error('invalidTransferRate');
}
return this;
};
// Add flags to a transaction.
// --> flags: undefined, _flag_, or [ _flags_ ]
Transaction.prototype.setFlags = function(flags) {
if (!flags) return this;
var transaction_flags = Transaction.flags[this.tx_json.TransactionType];
var flag_set = Array.isArray(flags) ? flags : Array.prototype.slice.call(arguments);
// We plan to not define this field on new Transaction.
if (this.tx_json.Flags === void(0)) {
this.tx_json.Flags = 0;
}
for (var i=0, l=flag_set.length; i<l; i++) {
var flag = flag_set[i];
if (transaction_flags.hasOwnProperty(flag)) {
this.tx_json.Flags += transaction_flags[flag];
} else {
// XXX Immediately report an error or mark it.
}
}
return this;
};
// Options:
// .domain() NYI
// .flags()
// .message_key() NYI
// .transfer_rate()
// .wallet_locator() NYI
// .wallet_size() NYI
Transaction.prototype.accountSet = function(src) {
if (typeof src === 'object') {
var options = src;
src = options.source || options.from;
}
if (!UInt160.is_valid(src)) {
throw new Error('Source address invalid');
}
this.tx_json.TransactionType = 'AccountSet';
this.tx_json.Account = UInt160.json_rewrite(src);
return this;
};
Transaction.prototype.claim = function(src, generator, public_key, signature) {
if (typeof src === 'object') {
var options = src;
signature = options.signature;
public_key = options.public_key;
generator = options.generator;
src = options.source || options.from;
}
this.tx_json.TransactionType = 'Claim';
this.tx_json.Generator = generator;
this.tx_json.PublicKey = public_key;
this.tx_json.Signature = signature;
return this;
};
Transaction.prototype.offerCancel = function(src, sequence) {
if (typeof src === 'object') {
var options = src;
sequence = options.sequence;
src = options.source || options.from;
}
if (!UInt160.is_valid(src)) {
throw new Error('Source address invalid');
}
this.tx_json.TransactionType = 'OfferCancel';
this.tx_json.Account = UInt160.json_rewrite(src);
this.tx_json.OfferSequence = Number(sequence);
return this;
};
// Options:
// .set_flags()
// --> expiration : if not undefined, Date or Number
// --> cancel_sequence : if not undefined, Sequence
Transaction.prototype.offerCreate = function(src, taker_pays, taker_gets, expiration, cancel_sequence) {
if (typeof src === 'object') {
var options = src;
cancel_sequence = options.cancel_sequence;
expiration = options.expiration;
taker_gets = options.taker_gets || options.sell;
taker_pays = options.taker_pays || options.buy;
src = options.source || options.from;
}
if (!UInt160.is_valid(src)) {
throw new Error('Source address invalid');
}
this.tx_json.TransactionType = 'OfferCreate';
this.tx_json.Account = UInt160.json_rewrite(src);
this.tx_json.TakerPays = Amount.json_rewrite(taker_pays);
this.tx_json.TakerGets = Amount.json_rewrite(taker_gets);
if (expiration) {
switch (expiration.constructor) {
case Date:
//offset = (new Date(2000, 0, 1).getTime()) - (new Date(1970, 0, 1).getTime());
this.tx_json.Expiration = expiration.getTime() - 946684800000;
break;
case Number:
this.tx_json.Expiration = expiration;
break;
}
}
if (cancel_sequence) {
this.tx_json.OfferSequence = Number(cancel_sequence);
}
return this;
};
Transaction.prototype.passwordFund = function(src, dst) {
if (typeof src === 'object') {
var options = src;
dst = options.destination || options.to;
src = options.source || options.from;
}
if (!UInt160.is_valid(dst)) {
throw new Error('Destination address invalid');
}
this.tx_json.TransactionType = 'PasswordFund';
this.tx_json.Destination = UInt160.json_rewrite(dst);
return this;
};
Transaction.prototype.passwordSet = function(src, authorized_key, generator, public_key, signature) {
if (typeof src === 'object') {
var options = src;
signature = options.signature;
public_key = options.public_key;
generator = options.generator;
authorized_key = options.authorized_key;
src = options.source || options.from;
}
if (!UInt160.is_valid(src)) {
throw new Error('Source address invalid');
}
this.tx_json.TransactionType = 'PasswordSet';
this.tx_json.RegularKey = authorized_key;
this.tx_json.Generator = generator;
this.tx_json.PublicKey = public_key;
this.tx_json.Signature = signature;
return this;
};
// Construct a 'payment' transaction.
//
// When a transaction is submitted:
// - If the connection is reliable and the server is not merely forwarding and is not malicious,
// --> src : UInt160 or String
// --> dst : UInt160 or String
// --> deliver_amount : Amount or String.
//
// Options:
// .paths()
// .build_path()
// .destination_tag()
// .path_add()
// .secret()
// .send_max()
// .set_flags()
// .source_tag()
Transaction.prototype.payment = function(src, dst, amount) {
if (typeof src === 'object') {
var options = src;
amount = options.amount;
dst = options.destination || options.to;
src = options.source || options.from;
if (options.invoiceID) {
this.invoiceID(options.invoiceID);
}
}
if (!UInt160.is_valid(src)) {
throw new Error('Payment source address invalid');
}
if (!UInt160.is_valid(dst)) {
throw new Error('Payment destination address invalid');
}
if (/^[\d]+[A-Z]{3}$/.test(amount)) {
amount = Amount.from_human(amount);
}
this.tx_json.TransactionType = 'Payment';
this.tx_json.Account = UInt160.json_rewrite(src);
this.tx_json.Amount = Amount.json_rewrite(amount);
this.tx_json.Destination = UInt160.json_rewrite(dst);
return this;
};
Transaction.prototype.trustSet =
Transaction.prototype.rippleLineSet = function(src, limit, quality_in, quality_out) {
if (typeof src === 'object') {
var options = src;
quality_out = options.quality_out;
quality_in = options.quality_in;
limit = options.limit;
src = options.source || options.from;
}
if (!UInt160.is_valid(src)) {
throw new Error('Source address invalid');
}
this.tx_json.TransactionType = 'TrustSet';
this.tx_json.Account = UInt160.json_rewrite(src);
if (limit !== void(0)) {
this.tx_json.LimitAmount = Amount.json_rewrite(limit);
}
if (quality_in) {
this.tx_json.QualityIn = quality_in;
}
if (quality_out) {
this.tx_json.QualityOut = quality_out;
}
// XXX Throw an error if nothing is set.
return this;
};
Transaction.prototype.walletAdd = function(src, amount, authorized_key, public_key, signature) {
if (typeof src === 'object') {
var options = src;
signature = options.signature;
public_key = options.public_key;
authorized_key = options.authorized_key;
amount = options.amount;
src = options.source || options.from;
}
if (!UInt160.is_valid(src)) {
throw new Error('Source address invalid');
}
this.tx_json.TransactionType = 'WalletAdd';
this.tx_json.Amount = Amount.json_rewrite(amount);
this.tx_json.RegularKey = authorized_key;
this.tx_json.PublicKey = public_key;
this.tx_json.Signature = signature;
return this;
};
// Submit a transaction to the network.
Transaction.prototype.submit = function(callback) {
var self = this;
this.callback = (typeof callback === 'function') ? callback : function(){};
function transactionError(error, message) {
if (!(error instanceof RippleError)) {
error = new RippleError(error, message);
}
self.callback(error);
};
this._errorHandler = transactionError;
function transactionSuccess(message) {
self.callback(null, message);
};
this._successHandler = transactionSuccess;
this.on('error', function(){});
var account = this.tx_json.Account;
if (typeof account !== 'string') {
this.emit('error', new RippleError('tejInvalidAccount', 'Account is unspecified'));
} else {
// YYY Might check paths for invalid accounts.
this.remote.account(account).submit(this);
}
return this;
};
Transaction.prototype.transactionManager = function() {
return this.remote.account(this.tx_json.Account)._transactionManager;
};
Transaction.prototype.abort = function(callback) {
if (!this.finalized) {
var callback = (typeof callback === 'function') ? callback : function(){};
this.once('final', callback);
this.emit('abort');
}
};
Transaction.prototype.iff = function(fn) {
this._iff = fn;
};
Transaction.prototype.summary = function() {
return Transaction.summary.call(this);
};
Transaction.summary = function() {
var result = {
tx_json: this.tx_json,
clientID: this._clientID,
submittedIDs: this.submittedIDs,
submissionAttempts: this.attempts,
submitIndex: this.submitIndex,
initialSubmitIndex: this.initialSubmitIndex,
lastLedgerSequence: this.lastLedgerSequence,
state: this.state,
server: this._server ? this._server._opts.url : void(0),
finalized: this.finalized
}
if (this.result) {
result.result = {
engine_result : this.result.engine_result,
engine_result_message: this.result.engine_result_message,
ledger_hash : this.result.ledger_hash,
ledger_index : this.result.ledger_index,
transaction_hash : this.result.tx_json.hash
}
}
return result;
};
exports.Transaction = Transaction;
// vim:sw=2:sts=2:ts=8:et

View File

@@ -0,0 +1,606 @@
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var Transaction = require('./transaction').Transaction;
var RippleError = require('./rippleerror').RippleError;
var PendingQueue = require('./transactionqueue').TransactionQueue;
/**
* @constructor TransactionManager
* @param {Account} account
*/
function TransactionManager(account) {
EventEmitter.call(this);
var self = this;
this._account = account;
this._accountID = account._account_id;
this._remote = account._remote;
this._nextSequence = void(0);
this._maxFee = this._remote.max_fee;
this._submissionTimeout = this._remote._submission_timeout;
this._pending = new PendingQueue;
// Query remote server for next account sequence number
this._loadSequence();
function transactionReceived(res) {
var transaction = TransactionManager.normalizeTransaction(res);
var sequence = transaction.transaction.Sequence;
var hash = transaction.transaction.hash;
if (!transaction.validated) return;
self._pending.addReceivedSequence(sequence);
// ND: we need to check against all submissions IDs
var submission = self._pending.getSubmission(hash);
self._remote._trace('transactionmanager: transaction_received:', transaction.transaction);
if (submission) {
// ND: A `success` handler will `finalize` this later
submission.emit('success', transaction);
} else {
self._pending.addReceivedId(hash, transaction);
}
};
this._account.on('transaction-outbound', transactionReceived);
function adjustFees(loadData, server) {
// ND: note, that `Fee` is a component of a transactionID
self._pending.forEach(function(pending) {
var shouldAdjust = pending._server === server
&& self._remote.local_fee && pending.tx_json.Fee;
if (!shouldAdjust) return;
var oldFee = pending.tx_json.Fee;
var newFee = server.computeFee(pending);
if (Number(newFee) > self._maxFee) {
return pending.once('presubmit', function() {
pending.emit('error', 'tejMaxFeeExceeded');
});
}
pending.tx_json.Fee = newFee;
pending.emit('fee_adjusted', oldFee, newFee);
self._remote._trace('transactionmanager: adjusting_fees:', pending.tx_json, oldFee, newFee);
});
};
this._remote.on('load_changed', adjustFees);
function updatePendingStatus(ledger) {
self._pending.forEach(function(pending) {
switch (ledger.ledger_index - pending.submitIndex) {
case 8:
pending.emit('lost', ledger);
self._remote._trace('transactionmanager: update_pending:', pending.tx_json);
break;
case 4:
pending.emit('missing', ledger);
break;
}
});
};
this._remote.on('ledger_closed', updatePendingStatus);
function remoteReconnected(callback) {
var callback = (typeof callback === 'function') ? callback : function(){};
if (!self._pending.length) {
return callback();
}
//Load account transaction history
var options = {
account: self._accountID,
ledger_index_min: -1,
ledger_index_max: -1,
binary: true,
limit: 100,
filter: 'outbound'
}
function accountTx(err, transactions) {
if (!err && Array.isArray(transactions.transactions)) {
transactions.transactions.forEach(transactionReceived);
}
self._remote.on('ledger_closed', updatePendingStatus);
//Load next transaction sequence
self._loadSequence(function sequenceLoaded() {
self._resubmit();
});
callback();
};
self._remote.requestAccountTx(options, accountTx);
self.emit('reconnect');
};
function remoteDisconnected() {
self._remote.once('connect', remoteReconnected);
self._remote.removeListener('ledger_closed', updatePendingStatus);
};
this._remote.on('disconnect', remoteDisconnected);
function saveTransaction(transaction) {
self._remote.storage.saveTransaction(transaction.summary());
};
if (this._remote.storage) {
this.on('save', saveTransaction);
}
};
util.inherits(TransactionManager, EventEmitter);
//Normalize transactions received from account
//transaction stream and account_tx
TransactionManager.normalizeTransaction = function(tx) {
var transaction = tx;
if (!tx.engine_result) {
// account_tx
transaction = {
engine_result: tx.meta.TransactionResult,
transaction: tx.tx,
hash: tx.tx.hash,
ledger_index: tx.tx.ledger_index,
meta: tx.meta,
type: 'transaction',
validated: true
}
transaction.result = transaction.engine_result;
transaction.result_message = transaction.engine_result_message;
}
transaction.metadata = transaction.meta;
if (!transaction.tx_json) {
transaction.tx_json = transaction.transaction;
}
return transaction;
};
//Fill an account transaction sequence
TransactionManager.prototype._fillSequence = function(tx, callback) {
var self = this;
function submitFill(sequence, callback) {
var fill = self._remote.transaction();
fill.account_set(self._accountID);
fill.tx_json.Sequence = sequence;
fill.once('submitted', callback);
fill.submit();
};
function sequenceLoaded(err, sequence) {
if (typeof sequence !== 'number') {
callback(new Error('Failed to fetch account transaction sequence'));
return;
}
var sequenceDif = tx.tx_json.Sequence - sequence;
var submitted = 0;
for (var i=sequence; i<tx.tx_json.Sequence; i++) {
submitFill(i, function() {
if (++submitted === sequenceDif) {
callback();
}
});
}
};
this._loadSequence(sequenceLoaded);
};
TransactionManager.prototype._loadSequence = function(callback) {
var self = this;
function sequenceLoaded(err, sequence) {
if (typeof sequence === 'number') {
self._nextSequence = sequence;
self.emit('sequence_loaded', sequence);
if (typeof callback === 'function') {
callback(err, sequence);
}
} else {
setTimeout(function() {
self._loadSequence(callback);
}, 1000 * 3);
}
};
this._account.getNextSequence(sequenceLoaded);
};
TransactionManager.prototype._resubmit = function(ledgers, pending) {
var self = this;
var pending = pending ? [ pending ] : this._pending;
var ledgers = Number(ledgers) || 0;
function resubmitTransaction(pending) {
if (!pending || pending.finalized) {
// Transaction has been finalized, nothing to do
return;
}
var hashCached = pending.findId(self._pending._idCache);
self._remote._trace('transactionmanager: resubmit:', pending.tx_json);
if (hashCached) {
return pending.emit('success', hashCached);
}
while (self._pending.hasSequence(pending.tx_json.Sequence)) {
//Sequence number has been consumed by another transaction
pending.tx_json.Sequence += 1;
self._remote._trace('transactionmanager: incrementing sequence:', pending.tx_json);
}
self._request(pending);
};
function resubmitTransactions() {
;(function nextTransaction(i) {
var transaction = pending[i];
if (!(transaction instanceof Transaction)) return;
transaction.once('submitted', function(m) {
transaction.emit('resubmitted', m);
self._loadSequence();
if (++i < pending.length) nextTransaction(i);
});
resubmitTransaction(transaction);
})(0);
};
this._waitLedgers(ledgers, resubmitTransactions);
};
TransactionManager.prototype._waitLedgers = function(ledgers, callback) {
if (ledgers < 1) return callback();
var self = this;
var closes = 0;
function ledgerClosed() {
if (++closes === ledgers) {
self._remote.removeListener('ledger_closed', ledgerClosed);
callback();
}
};
this._remote.on('ledger_closed', ledgerClosed);
};
TransactionManager.prototype._request = function(tx) {
var self = this;
var remote = this._remote;
if (tx.attempts > 10) {
return tx.emit('error', new RippleError('tejAttemptsExceeded'));
}
if (tx.attempts > 0 && !remote.local_signing) {
// && tx.submittedTxnIDs.length != tx.attempts
// ^^^ Above commented out intentionally
// ^^^^ We might be a bit cleverer about allowing this in SOME cases, but
// it's not really worth it, and would be prone to error. Use
// `local_signing`
var message = ''
+ 'It is not possible to resubmit transactions automatically safely without '
+ 'synthesizing the transactionID locally. See `local_signing` config option';
return tx.emit('error', new RippleError('tejLocalSigningRequired', message));
}
tx.emit('presubmit');
if (tx.finalized) return;
tx.submitIndex = this._remote._ledger_current_index;
if (tx.attempts === 0) {
tx.initialSubmitIndex = tx.submitIndex;
}
if (!tx._setLastLedger) {
// Honor LastLedgerSequence set by user of API. If
// left unset by API, bump LastLedgerSequence
tx.tx_json.LastLedgerSequence = tx.submitIndex + 8;
}
tx.lastLedgerSequence = tx.tx_json.LastLedgerSequence;
var submitRequest = remote.requestSubmit();
if (remote.local_signing) {
tx.sign();
// TODO: We are serializing twice, when we could/should be feeding the
// tx_blob to `tx.hash()` which rebuilds it to sign it.
submitRequest.tx_blob(tx.serialize().to_hex());
// ND: ecdsa produces a random `TxnSignature` field value, a component of
// the hash. Attempting to identify a transaction via a hash synthesized
// locally while using remote signing is inherently flawed.
tx.addId(tx.hash());
} else {
// ND: `build_path` is completely ignored when doing local signing as
// `Paths` is a component of the signed blob, the `tx_blob` is signed,
// sealed and delivered, and the txn unmodified.
// TODO: perhaps an exception should be raised if build_path is attempted
// while local signing
submitRequest.build_path(tx._build_path);
submitRequest.secret(tx._secret);
submitRequest.tx_json(tx.tx_json);
}
remote._trace('transactionmanager: submit:', tx.tx_json);
function transactionProposed(message) {
if (tx.finalized) return;
// If server is honest, don't expect a final if rejected.
message.rejected = tx.isRejected(message.engine_result_code);
tx.emit('proposed', message);
};
function transactionFailed(message) {
if (tx.finalized) return;
switch (message.engine_result) {
case 'tefPAST_SEQ':
self._resubmit(1, tx);
break;
default:
tx.emit('error', message);
}
};
function transactionRetry(message) {
if (tx.finalized) return;
self._fillSequence(tx, function() {
self._resubmit(1, tx);
});
};
function transactionFeeClaimed(message) {
if (tx.finalized) return;
tx.emit('error', message);
};
function transactionFailedLocal(message) {
if (tx.finalized) return;
var shouldAdjustFee = self._remote.local_fee
&& (message.engine_result === 'telINSUF_FEE_P');
if (shouldAdjustFee) {
self._resubmit(1, tx);
} else {
submissionError(message);
}
};
function submissionError(error) {
// Finalized (e.g. aborted) transactions must stop all activity
if (tx.finalized) return;
if (TransactionManager._isTooBusy(error)) {
self._resubmit(1, tx);
} else {
self._nextSequence--;
tx.emit('error', error);
}
};
function submitted(message) {
// Finalized (e.g. aborted) transactions must stop all activity
if (tx.finalized) return;
// ND: If for some unknown reason our hash wasn't computed correctly this is
// an extra measure.
if (message.tx_json && message.tx_json.hash) {
tx.addId(message.tx_json.hash);
}
message.result = message.engine_result || '';
tx.result = message;
remote._trace('transactionmanager: submit_response:', message);
tx.emit('submitted', message);
switch (message.result.slice(0, 3)) {
case 'tes':
transactionProposed(message);
break;
case 'tec':
transactionFeeClaimed(message);
break;
case 'ter':
transactionRetry(message);
break;
case 'tef':
transactionFailed(message);
break;
case 'tel':
transactionFailedLocal(message);
break;
default:
// tem
submissionError(message);
}
};
submitRequest.once('error', submitted);
submitRequest.once('success', submitted);
if (tx._server) {
submitRequest.server = tx._server;
}
if (typeof tx._iff !== 'function') {
submitTransaction();
} else {
return tx._iff(tx.summary(), function(err, proceed) {
if (err || !proceed) {
tx.emit('abort');
} else {
submitTransaction();
}
});
}
function requestTimeout() {
// ND: What if the response is just slow and we get a response that
// `submitted` above will cause to have concurrent resubmit logic streams?
// It's simpler to just mute handlers and look out for finalized
// `transaction` messages.
// ND: We should audit the code for other potential multiple resubmit
// streams. Connection/reconnection could be one? That's why it's imperative
// that ALL transactionIDs sent over network are tracked.
// Finalized (e.g. aborted) transactions must stop all activity
if (tx.finalized) return;
tx.emit('timeout');
if (remote._connected) {
remote._trace('transactionmanager: timeout:', tx.tx_json);
self._resubmit(3, tx);
}
};
function submitTransaction() {
if (tx.finalized) return;
submitRequest.timeout(self._submissionTimeout, requestTimeout);
submitRequest.request();
tx.attempts++;
tx.emit('postsubmit');
};
return submitRequest;
};
TransactionManager._isNoOp = function(transaction) {
return (typeof transaction === 'object')
&& (typeof transaction.tx_json === 'object')
&& (transaction.tx_json.TransactionType === 'AccountSet')
&& (transaction.tx_json.Flags === 0);
};
TransactionManager._isRemoteError = function(error) {
return (typeof error === 'object')
&& (error.error === 'remoteError')
&& (typeof error.remote === 'object');
};
TransactionManager._isNotFound = function(error) {
return TransactionManager._isRemoteError(error)
&& /^(txnNotFound|transactionNotFound)$/.test(error.remote.error);
};
TransactionManager._isTooBusy = function(error) {
return TransactionManager._isRemoteError(error)
&& (error.remote.error === 'tooBusy');
};
/**
* Entry point for TransactionManager submission
*
* @param {Object} tx
*/
TransactionManager.prototype.submit = function(tx) {
var self = this;
var remote = this._remote;
// If sequence number is not yet known, defer until it is.
if (typeof this._nextSequence === 'undefined') {
function sequenceLoaded() {
self.submit(tx);
};
this.once('sequence_loaded', sequenceLoaded);
return;
}
// Finalized (e.g. aborted) transactions must stop all activity
if (tx.finalized) return;
function cleanup(message) {
// ND: We can just remove this `tx` by identity
self._pending.remove(tx);
tx.emit('final', message);
remote._trace('transactionmanager: finalize_transaction:', tx.tx_json);
};
tx.once('cleanup', cleanup);
tx.on('save', function() {
self.emit('save', tx);
});
tx.once('error', function(message) {
tx._errorHandler(message);
});
tx.once('success', function(message) {
tx._successHandler(message);
});
tx.once('abort', function() {
tx.emit('error', new RippleError('tejAbort', 'Transaction aborted'));
});
if (typeof tx.tx_json.Sequence !== 'number') {
tx.tx_json.Sequence = this._nextSequence++;
}
tx.attempts = 0;
// Attach secret, associate transaction with a server, attach fee
tx.complete();
var fee = Number(tx.tx_json.Fee);
if (!tx._secret && !tx.tx_json.TxnSignature) {
tx.emit('error', new RippleError('tejSecretUnknown', 'Missing secret'));
} else if (!remote.trusted && !remote.local_signing) {
tx.emit('error', new RippleError('tejServerUntrusted', 'Attempt to give secret to untrusted server'));
} else if (fee && fee > this._maxFee) {
tx.emit('error', new RippleError('tejMaxFeeExceeded', 'Max fee exceeded'));
} else {
// ND: this is the ONLY place we put the tx into the queue. The
// TransactionQueue queue is merely a list, so any mutations to tx._hash
// will cause subsequent look ups (eg. inside 'transaction-outbound'
// validated transaction clearing) to fail.
this._pending.push(tx);
this._request(tx);
}
};
exports.TransactionManager = TransactionManager;

View File

@@ -0,0 +1,144 @@
/**
* Manager for pending transactions
*/
var Transaction = require('./transaction').Transaction;
function TransactionQueue() {
var self = this;
this._queue = [ ];
this._idCache = { };
this._sequenceCache = { };
this._save = void(0);
};
TransactionQueue.prototype.clearCache = function() {
this._idCache = { };
this._sequenceCache = { };
};
TransactionQueue.prototype.getMinLedger = function() {
var minLedger = Infinity;
for (var i=0; i<this._queue.length; i++) {
var submitIndex = this._queue[i].submitIndex;
if (typeof submitIndex !== 'number') {
// If any pending transactions don't have a submit index,
// return -1 for scanning all previous transactions
minLedger = -1;
break;
}
if (submitIndex < minLedger) {
minLedger = submitIndex;
}
};
if (!isFinite(minLedger)) minLedger = -1;
if (minLedger !== -1) minLedger -= 1;
return minLedger;
};
TransactionQueue.prototype.save = function() {
if (typeof this._save !== 'function') return;
this._save(this._queue.map(function(tx) {
return {
tx_json: tx.tx_json,
submittedIDs: tx.submittedIDs
}
}));
};
/**
* Store received (validated) sequence
*/
TransactionQueue.prototype.addReceivedSequence = function(sequence) {
this._sequenceCache[sequence] = true;
};
/**
* Store received (validated) ID transaction
*/
TransactionQueue.prototype.addReceivedId = function(id, transaction) {
this._idCache[id] = transaction;
};
/**
* Get received (validated) transaction by ID
*/
TransactionQueue.prototype.getReceived = function(id) {
return this._idCache[id];
};
/**
* Check that sequence number has been consumed by a validated
* transaction
*/
TransactionQueue.prototype.hasSequence = function(sequence) {
return this._sequenceCache[sequence] || false;
};
/**
* Get a submitted transaction by ID. Transactions
* may have multiple associated IDs.
*/
TransactionQueue.prototype.getSubmission = function(id, callback) {
var result = false;
for (var i=0, tx; tx=this._queue[i]; i++) {
if (~tx.submittedIDs.indexOf(id)) {
result = tx;
break;
}
}
return result;
};
/**
* Remove a transaction from the queue
*/
TransactionQueue.prototype.remove = function(tx) {
// ND: We are just removing the Transaction by identity
var i = this._queue.length;
while (i--) {
if (this._queue[i] === tx) {
this._queue.splice(i, 1);
break;
}
}
if (!this._queue.length) {
this.clearCache();
}
this.save();
};
TransactionQueue.prototype.push = function(tx) {
this._queue.push(tx);
this.save();
};
TransactionQueue.prototype.forEach = function(fn) {
this._queue.forEach(fn);
};
TransactionQueue.prototype.length = function() {
return this._queue.length;
};
exports.TransactionQueue = TransactionQueue;

View File

@@ -1,11 +1,8 @@
var sjcl = require('../../build/sjcl');
var utils = require('./utils');
var sjcl = utils.sjcl;
var config = require('./config');
var jsbn = require('./jsbn');
var BigInteger = jsbn.BigInteger;
var nbi = jsbn.nbi;
var BigInteger = utils.jsbn.BigInteger;
var Base = require('./base').Base;
@@ -18,6 +15,8 @@ var Base = require('./base').Base;
var UInt = function () {
// Internal form: NaN or BigInteger
this._value = NaN;
this._update();
};
UInt.json_rewrite = function (j, opts) {
@@ -60,6 +59,15 @@ UInt.from_bits = function (j) {
}
};
// Return a new UInt from j.
UInt.from_bytes = function (j) {
if (j instanceof this) {
return j.clone();
} else {
return (new this()).parse_bytes(j);
}
};
// Return a new UInt from j.
UInt.from_bn = function (j) {
if (j instanceof this) {
@@ -69,6 +77,15 @@ UInt.from_bn = function (j) {
}
};
// Return a new UInt from j.
UInt.from_number = function (j) {
if (j instanceof this) {
return j.clone();
} else {
return (new this()).parse_number(j);
}
};
UInt.is_valid = function (j) {
return this.from_json(j).is_valid();
};
@@ -81,6 +98,8 @@ UInt.prototype.clone = function () {
UInt.prototype.copyTo = function (d) {
d._value = this._value;
if ("function" === typeof d._update) d._update();
return d;
};
@@ -96,6 +115,20 @@ UInt.prototype.is_zero = function () {
return this._value.equals(BigInteger.ZERO);
};
/**
* Update any derivative values.
*
* This allows subclasses to maintain caches of any data that they derive from
* the main _value. For example, the Currency class keeps the currency type, the
* currency code and other information about the currency cached.
*
* The reason for keeping this mechanism in this class is so every subclass can
* call it whenever it modifies the internal state.
*/
UInt.prototype._update = function () {
// Nothing to do by default. Subclasses will override this.
};
// value = NaN on error.
UInt.prototype.parse_generic = function (j) {
// Canonicalize and validate
@@ -108,7 +141,7 @@ UInt.prototype.parse_generic = function (j) {
case this.constructor.STR_ZERO:
case this.constructor.ACCOUNT_ZERO:
case this.constructor.HEX_ZERO:
this._value = nbi();
this._value = BigInteger.valueOf();
break;
case "1":
@@ -123,9 +156,6 @@ UInt.prototype.parse_generic = function (j) {
if ('string' !== typeof j) {
this._value = NaN;
}
else if (j[0] === "r") {
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
}
else if (this.constructor.width === j.length) {
this._value = new BigInteger(utils.stringToArray(j), 256);
}
@@ -138,6 +168,8 @@ UInt.prototype.parse_generic = function (j) {
}
}
this._update();
return this;
};
@@ -149,6 +181,8 @@ UInt.prototype.parse_hex = function (j) {
this._value = NaN;
}
this._update();
return this;
};
@@ -157,12 +191,28 @@ UInt.prototype.parse_bits = function (j) {
this._value = NaN;
} else {
var bytes = sjcl.codec.bytes.fromBits(j);
this._value = new BigInteger(bytes, 256);
this.parse_bytes(bytes);
}
this._update();
return this;
};
UInt.prototype.parse_bytes = function (j) {
if (!Array.isArray(j) || j.length !== this.constructor.width) {
this._value = NaN;
} else {
this._value = new BigInteger([0].concat(j), 256);
}
this._update();
return this;
};
UInt.prototype.parse_json = UInt.prototype.parse_hex;
UInt.prototype.parse_bn = function (j) {
@@ -174,6 +224,23 @@ UInt.prototype.parse_bn = function (j) {
this._value = NaN;
}
this._update();
return this;
};
UInt.prototype.parse_number = function (j) {
this._value = NaN;
if ("number" === typeof j &&
j === +j &&
j > 0) {
// XXX Better, faster way to get BigInteger from JS int?
this._value = new BigInteger(""+j);
}
this._update();
return this;
};
@@ -198,7 +265,6 @@ UInt.prototype.to_hex = function () {
return null;
var bytes = this.to_bytes();
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(bytes)).toUpperCase();
};

29
src/js/ripple/uint128.js Normal file
View File

@@ -0,0 +1,29 @@
var sjcl = require('./utils').sjcl;
var utils = require('./utils');
var config = require('./config');
var extend = require('extend');
var BigInteger = utils.jsbn.BigInteger;
var UInt = require('./uint').UInt,
Base = require('./base').Base;
//
// UInt128 support
//
var UInt128 = extend(function () {
// Internal form: NaN or BigInteger
this._value = NaN;
}, UInt);
UInt128.width = 16;
UInt128.prototype = extend({}, UInt.prototype);
UInt128.prototype.constructor = UInt128;
var HEX_ZERO = UInt128.HEX_ZERO = "00000000000000000000000000000000";
var HEX_ONE = UInt128.HEX_ONE = "00000000000000000000000000000000";
var STR_ZERO = UInt128.STR_ZERO = utils.hexToString(HEX_ZERO);
var STR_ONE = UInt128.STR_ONE = utils.hexToString(HEX_ONE);
exports.UInt128 = UInt128;

107
src/js/ripple/uint160.js Normal file
View File

@@ -0,0 +1,107 @@
var sjcl = require('./utils').sjcl;
var utils = require('./utils');
var config = require('./config');
var extend = require('extend');
var BigInteger = utils.jsbn.BigInteger;
var UInt = require('./uint').UInt;
var Base = require('./base').Base;
//
// UInt160 support
//
var UInt160 = extend(function () {
// Internal form: NaN or BigInteger
this._value = NaN;
this._version_byte = void(0);
this._update();
}, UInt);
UInt160.width = 20;
UInt160.prototype = extend({}, UInt.prototype);
UInt160.prototype.constructor = UInt160;
var ACCOUNT_ZERO = UInt160.ACCOUNT_ZERO = 'rrrrrrrrrrrrrrrrrrrrrhoLvTp';
var ACCOUNT_ONE = UInt160.ACCOUNT_ONE = 'rrrrrrrrrrrrrrrrrrrrBZbvji';
var HEX_ZERO = UInt160.HEX_ZERO = '0000000000000000000000000000000000000000';
var HEX_ONE = UInt160.HEX_ONE = '0000000000000000000000000000000000000001';
var STR_ZERO = UInt160.STR_ZERO = utils.hexToString(HEX_ZERO);
var STR_ONE = UInt160.STR_ONE = utils.hexToString(HEX_ONE);
UInt160.prototype.set_version = function (j) {
this._version_byte = j;
return this;
};
UInt160.prototype.get_version = function () {
return this._version_byte;
};
// value = NaN on error.
UInt160.prototype.parse_json = function (j) {
// Canonicalize and validate
if (config.accounts && j in config.accounts) {
j = config.accounts[j].account;
}
if (typeof j === 'number' && !isNaN(j)) {
// Allow raw numbers - DEPRECATED
// This is used mostly by the test suite and is supported
// as a legacy feature only. DO NOT RELY ON THIS BEHAVIOR.
this._value = new BigInteger(String(j));
this._version_byte = Base.VER_ACCOUNT_ID;
} else if (typeof j !== 'string') {
this._value = NaN;
} else if (j[0] === 'r') {
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
this._version_byte = Base.VER_ACCOUNT_ID;
} else {
this.parse_hex(j);
}
this._update();
return this;
};
UInt160.prototype.parse_generic = function (j) {
UInt.prototype.parse_generic.call(this, j);
if (isNaN(this._value)) {
if ("string" === typeof j && j[0] === 'r') {
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
}
}
this._update();
return this;
};
// XXX Json form should allow 0 and 1, C++ doesn't currently allow it.
UInt160.prototype.to_json = function (opts) {
opts = opts || {};
if (this._value instanceof BigInteger) {
// If this value has a type, return a Base58 encoded string.
if ("number" === typeof this._version_byte) {
var output = Base.encode_check(this._version_byte, this.to_bytes());
if (opts.gateways && output in opts.gateways) {
output = opts.gateways[output];
}
return output;
} else {
return this.to_hex();
}
}
return NaN;
};
exports.UInt160 = UInt160;
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,12 +1,9 @@
var sjcl = require('../../build/sjcl');
var sjcl = require('./utils').sjcl;
var utils = require('./utils');
var config = require('./config');
var jsbn = require('./jsbn');
var extend = require('extend');
var BigInteger = jsbn.BigInteger;
var nbi = jsbn.nbi;
var BigInteger = utils.jsbn.BigInteger;
var UInt = require('./uint').UInt,
Base = require('./base').Base;

158
src/js/ripple/utils.js Normal file
View File

@@ -0,0 +1,158 @@
Function.prototype.method = function(name, func) {
this.prototype[name] = func;
return this;
};
function filterErr(code, done) {
return function(e) {
done(e.code !== code ? e : void(0));
};
};
function throwErr(done) {
return function(e) {
if (e) {
throw e;
}
done();
};
};
function trace(comment, func) {
return function() {
console.log("%s: %s", trace, arguments.toString);
func(arguments);
};
};
function arraySet(count, value) {
var a = new Array(count);
for (var i=0; i<count; i++) {
a[i] = value;
}
return a;
};
function hexToString(h) {
var a = [];
var i = 0;
if (h.length % 2) {
a.push(String.fromCharCode(parseInt(h.substring(0, 1), 16)));
i = 1;
}
for (; i<h.length; i+=2) {
a.push(String.fromCharCode(parseInt(h.substring(i, i+2), 16)));
}
return a.join('');
};
function stringToHex(s) {
var result = '';
for (var i=0; i<s.length; i++) {
var b = s.charCodeAt(i);
result += b < 16 ? '0' + b.toString(16) : b.toString(16);
}
return result;
};
function stringToArray(s) {
var a = new Array(s.length);
for (var i=0; i<a.length; i+=1) {
a[i] = s.charCodeAt(i);
}
return a;
};
function hexToArray(h) {
return stringToArray(hexToString(h));
};
function chunkString(str, n, leftAlign) {
var ret = [];
var i=0, len=str.length;
if (leftAlign) {
i = str.length % n;
if (i) {
ret.push(str.slice(0, i));
}
}
for(; i<len; i+=n) {
ret.push(str.slice(i, n + i));
}
return ret;
};
function assert(assertion, msg) {
if (!assertion) {
throw new Error("Assertion failed" + (msg ? ": "+msg : "."));
}
};
/**
* Return unique values in array.
*/
function arrayUnique(arr) {
var u = {}, a = [];
for (var i=0, l=arr.length; i<l; i++){
var k = arr[i];
if (u[k]) {
continue;
}
a.push(k);
u[k] = true;
}
return a;
};
/**
* Convert a ripple epoch to a JavaScript timestamp.
*
* JavaScript timestamps are unix epoch in milliseconds.
*/
function toTimestamp(rpepoch) {
return (rpepoch + 0x386D4380) * 1000;
};
/**
* Convert a JavaScript timestamp or Date to a Ripple epoch.
*
* JavaScript timestamps are unix epoch in milliseconds.
*/
function fromTimestamp(rpepoch) {
if (rpepoch instanceof Date) {
rpepoch = rpepoch.getTime();
}
return Math.round(rpepoch/1000) - 0x386D4380;
};
exports.trace = trace;
exports.arraySet = arraySet;
exports.hexToString = hexToString;
exports.hexToArray = hexToArray;
exports.stringToArray = stringToArray;
exports.stringToHex = stringToHex;
exports.chunkString = chunkString;
exports.assert = assert;
exports.arrayUnique = arrayUnique;
exports.toTimestamp = toTimestamp;
exports.fromTimestamp = fromTimestamp;
// Going up three levels is needed to escape the src-cov folder used for the
// test coverage stuff.
exports.sjcl = require('../../../build/sjcl');
exports.jsbn = require('../../../src/js/jsbn/jsbn');
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,144 +0,0 @@
var binformat = require('./binformat'),
sjcl = require('../../build/sjcl'),
extend = require('extend'),
stypes = require('./serializedtypes');
var UInt256 = require('./uint256').UInt256;
var SerializedObject = function () {
this.buffer = [];
this.pointer = 0;
};
SerializedObject.from_json = function (obj) {
var typedef;
var so = new SerializedObject();
// Create a copy of the object so we don't modify it
obj = extend({}, obj);
if ("number" === typeof obj.TransactionType) {
obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType);
if (!obj.TransactionType) {
throw new Error("Transaction type ID is invalid.");
}
}
if ("string" === typeof obj.TransactionType) {
typedef = binformat.tx[obj.TransactionType].slice();
obj.TransactionType = typedef.shift();
} else if ("undefined" !== typeof obj.LedgerEntryType) {
// XXX: TODO
throw new Error("Ledger entry binary format not yet implemented.");
} else throw new Error("Object to be serialized must contain either " +
"TransactionType or LedgerEntryType.");
so.serialize(typedef, obj);
return so;
};
SerializedObject.prototype.append = function (bytes) {
this.buffer = this.buffer.concat(bytes);
this.pointer += bytes.length;
};
SerializedObject.prototype.to_bits = function ()
{
return sjcl.codec.bytes.toBits(this.buffer);
};
SerializedObject.prototype.to_hex = function () {
return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase();
};
SerializedObject.prototype.serialize = function (typedef, obj)
{
// Ensure canonical order
typedef = SerializedObject._sort_typedef(typedef.slice());
// Serialize fields
for (var i = 0, l = typedef.length; i < l; i++) {
var spec = typedef[i];
this.serialize_field(spec, obj);
}
};
SerializedObject.prototype.signing_hash = function (prefix)
{
var sign_buffer = new SerializedObject();
stypes.Int32.serialize(sign_buffer, prefix);
sign_buffer.append(this.buffer);
return sign_buffer.hash_sha512_half();
};
SerializedObject.prototype.hash_sha512_half = function ()
{
var bits = sjcl.codec.bytes.toBits(this.buffer),
hash = sjcl.bitArray.bitSlice(sjcl.hash.sha512.hash(bits), 0, 256);
return UInt256.from_hex(sjcl.codec.hex.fromBits(hash));
};
SerializedObject.prototype.serialize_field = function (spec, obj)
{
spec = spec.slice();
var name = spec.shift(),
presence = spec.shift(),
field_id = spec.shift(),
Type = spec.shift();
if ("undefined" !== typeof obj[name]) {
//console.log(name, Type.id, field_id);
this.append(SerializedObject.get_field_header(Type.id, field_id));
try {
Type.serialize(this, obj[name]);
} catch (e) {
// Add field name to message and rethrow
e.message = "Error serializing '"+name+"': "+e.message;
throw e;
}
} else if (presence === binformat.REQUIRED) {
throw new Error('Missing required field '+name);
}
};
SerializedObject.get_field_header = function (type_id, field_id)
{
var buffer = [0];
if (type_id > 0xf) buffer.push(type_id & 0xff);
else buffer[0] += (type_id & 0xf) << 4;
if (field_id > 0xf) buffer.push(field_id & 0xff);
else buffer[0] += field_id & 0xf;
return buffer;
};
function sort_field_compare(a, b) {
// Sort by type id first, then by field id
return a[3].id !== b[3].id ?
a[3].id - b[3].id :
a[2] - b[2];
};
SerializedObject._sort_typedef = function (typedef) {
return typedef.sort(sort_field_compare);
};
SerializedObject.lookup_type_tx = function (id) {
for (var i in binformat.tx) {
if (!binformat.tx.hasOwnProperty(i)) continue;
if (binformat.tx[i][0] === id) {
return i;
}
}
return null;
};
exports.SerializedObject = SerializedObject;

View File

@@ -1,319 +0,0 @@
/**
* Type definitions for binary format.
*
* This file should not be included directly. Instead, find the format you're
* trying to parse or serialize in binformat.js and pass that to
* SerializedObject.parse() or SerializedObject.serialize().
*/
var extend = require('extend'),
utils = require('./utils'),
sjcl = require('../../build/sjcl');
var amount = require('./amount'),
UInt160 = amount.UInt160,
Amount = amount.Amount,
Currency= amount.Currency;
// Shortcuts
var hex = sjcl.codec.hex,
bytes = sjcl.codec.bytes;
var SerializedType = function (methods) {
extend(this, methods);
};
SerializedType.prototype.serialize_hex = function (so, hexData) {
var byteData = bytes.fromBits(hex.toBits(hexData));
this.serialize_varint(so, byteData.length);
so.append(byteData);
};
SerializedType.prototype.serialize_varint = function (so, val) {
if (val < 0) {
throw new Error("Variable integers are unsigned.");
}
if (val <= 192) {
so.append([val]);
} else if (val <= 12,480) {
val -= 193;
so.append([193 + (val >>> 8), val & 0xff]);
} else if (val <= 918744) {
val -= 12481;
so.append([
241 + (val >>> 16),
val >>> 8 & 0xff,
val & 0xff
]);
} else throw new Error("Variable integer overflow.");
};
var STInt8 = exports.Int8 = new SerializedType({
serialize: function (so, val) {
so.append([val & 0xff]);
},
parse: function (so) {
return so.read(1)[0];
}
});
var STInt16 = exports.Int16 = new SerializedType({
serialize: function (so, val) {
so.append([
val >>> 8 & 0xff,
val & 0xff
]);
},
parse: function (so) {
// XXX
throw new Error("Parsing Int16 not implemented");
}
});
var STInt32 = exports.Int32 = new SerializedType({
serialize: function (so, val) {
so.append([
val >>> 24 & 0xff,
val >>> 16 & 0xff,
val >>> 8 & 0xff,
val & 0xff
]);
},
parse: function (so) {
// XXX
throw new Error("Parsing Int32 not implemented");
}
});
var STInt64 = exports.Int64 = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Int64 not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Int64 not implemented");
}
});
var STHash128 = exports.Hash128 = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Hash128 not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Hash128 not implemented");
}
});
var STHash256 = exports.Hash256 = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Hash256 not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Hash256 not implemented");
}
});
var STHash160 = exports.Hash160 = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Hash160 not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Hash160 not implemented");
}
});
// Internal
var STCurrency = new SerializedType({
serialize: function (so, val) {
var currency = val.to_json();
if ("string" === typeof currency && currency.length === 3) {
var currencyCode = currency.toUpperCase(),
currencyData = utils.arraySet(20, 0);
if (!/^[A-Z]{3}$/.test(currencyCode)) {
throw new Error('Invalid currency code');
}
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
currencyData[13] = currencyCode.charCodeAt(1) & 0xff;
currencyData[14] = currencyCode.charCodeAt(2) & 0xff;
so.append(currencyData);
} else {
throw new Error('Tried to serialize invalid/unimplemented currency type.');
}
},
parse: function (so) {
// XXX
throw new Error("Parsing Currency not implemented");
}
});
var STAmount = exports.Amount = new SerializedType({
serialize: function (so, val) {
var amount = Amount.from_json(val);
if (!amount.is_valid()) {
throw new Error("Not a valid Amount object.");
}
// Amount (64-bit integer)
var valueBytes = utils.arraySet(8, 0);
if (amount.is_native()) {
var valueHex = amount._value.toString(16);
// Enforce correct length (64 bits)
if (valueHex.length > 16) {
throw new Error('Value out of bounds');
}
while (valueHex.length < 16) {
valueHex = "0" + valueHex;
}
valueBytes = bytes.fromBits(hex.toBits(valueHex));
// Clear most significant two bits - these bits should already be 0 if
// Amount enforces the range correctly, but we'll clear them anyway just
// so this code can make certain guarantees about the encoded value.
valueBytes[0] &= 0x3f;
if (!amount.is_negative()) valueBytes[0] |= 0x40;
} else {
var hi = 0, lo = 0;
// First bit: non-native
hi |= 1 << 31;
if (!amount.is_zero()) {
// Second bit: non-negative?
if (!amount.is_negative()) hi |= 1 << 30;
// Next eight bits: offset/exponent
hi |= ((97 + amount._offset) & 0xff) << 22;
// Remaining 52 bits: mantissa
hi |= amount._value.shiftRight(32).intValue() & 0x3fffff;
lo = amount._value.intValue() & 0xffffffff;
}
valueBytes = sjcl.codec.bytes.fromBits([hi, lo]);
}
so.append(valueBytes);
if (!amount.is_native()) {
// Currency (160-bit hash)
var currency = amount.currency();
STCurrency.serialize(so, currency);
// Issuer (160-bit hash)
so.append(amount.issuer().to_bytes());
}
},
parse: function (so) {
// XXX
throw new Error("Parsing Amount not implemented");
}
});
var STVL = exports.VariableLength = new SerializedType({
serialize: function (so, val) {
if ("string" === typeof val) this.serialize_hex(so, val);
else throw new Error("Unknown datatype.");
},
parse: function (so) {
// XXX
throw new Error("Parsing VL not implemented");
}
});
var STAccount = exports.Account = new SerializedType({
serialize: function (so, val) {
var account = UInt160.from_json(val);
this.serialize_hex(so, account.to_hex());
},
parse: function (so) {
// XXX
throw new Error("Parsing Account not implemented");
}
});
var STPathSet = exports.PathSet = new SerializedType({
typeBoundary: 0xff,
typeEnd: 0x00,
typeAccount: 0x01,
typeCurrency: 0x10,
typeIssuer: 0x20,
serialize: function (so, val) {
// XXX
for (var i = 0, l = val.length; i < l; i++) {
// Boundary
if (i) STInt8.serialize(so, this.typeBoundary);
for (var j = 0, l2 = val[i].length; j < l2; j++) {
var entry = val[i][j];
var type = 0;
if (entry.account) type |= this.typeAccount;
if (entry.currency) type |= this.typeCurrency;
if (entry.issuer) type |= this.typeIssuer;
STInt8.serialize(so, type);
if (entry.account) {
so.append(UInt160.from_json(entry.account).to_bytes());
}
if (entry.currency) {
var currency = Currency.from_json(entry.currency);
STCurrency.serialize(so, currency);
}
if (entry.issuer) {
so.append(UInt160.from_json(entry.issuer).to_bytes());
}
}
}
STInt8.serialize(so, this.typeEnd);
},
parse: function (so) {
// XXX
throw new Error("Parsing PathSet not implemented");
}
});
var STVector256 = exports.Vector256 = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Vector256 not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Vector256 not implemented");
}
});
var STObject = exports.Object = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Object not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Object not implemented");
}
});
var STArray = exports.Array = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Array not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Array not implemented");
}
});

View File

@@ -1,255 +0,0 @@
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var utils = require('./utils');
var Server = function (remote, cfg)
{
EventEmitter.call(this);
if ("object" !== typeof cfg || "string" !== typeof cfg.url) {
throw new Error("Invalid server configuration.");
}
this._remote = remote;
this._cfg = cfg;
this._ws = null;
this._connected = false;
this._should_connect = false;
this._state = null;
this._id = 0;
this._retry = 0;
this._requests = {};
this.on('message', this._handle_message.bind(this));
this.on('response_subscribe', this._handle_response_subscribe.bind(this));
};
util.inherits(Server, EventEmitter);
/**
* Server states that we will treat as the server being online.
*
* Our requirements are that the server can process transactions and notify
* us of changes.
*/
Server.online_states = [
'proposing',
'validating',
'full'
];
Server.prototype.connect = function ()
{
var self = this;
// We don't connect if we believe we're already connected. This means we have
// 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,
// we will automatically reconnect.
if (this._connected === true) return;
if (this._remote.trace) console.log("server: connect: %s", this._cfg.url);
// Ensure any existing socket is given the command to close first.
if (this._ws) this._ws.close();
var WebSocket = require('ws');
var ws = this._ws = new WebSocket(this._cfg.url);
this._should_connect = true;
self.emit('connecting');
ws.onopen = function () {
// If we are no longer the active socket, simply ignore any event
if (ws !== self._ws) return;
self.emit('socket_open');
// Subscribe to events
var request = self._remote._server_prepare_subscribe();
self.request(request);
};
ws.onerror = function (e) {
// If we are no longer the active socket, simply ignore any event
if (ws !== self._ws) return;
if (self._remote.trace) console.log("server: onerror: %s", e.data || e);
// Most connection errors for WebSockets are conveyed as "close" events with
// code 1006. This is done for security purposes and therefore unlikely to
// ever change.
// This means that this handler is hardly ever called in practice. If it is,
// it probably means the server's WebSocket implementation is corrupt, or
// the connection is somehow producing corrupt data.
// Most WebSocket applications simply log and ignore this error. Once we
// support for multiple servers, we may consider doing something like
// lowering this server's quality score.
// However, in Node.js this event may be triggered instead of the close
// event, so we need to handle it.
handleConnectionClose();
};
// Failure to open.
ws.onclose = function () {
// If we are no longer the active socket, simply ignore any event
if (ws !== self._ws) return;
if (self._remote.trace) console.log("server: onclose: %s", ws.readyState);
handleConnectionClose();
};
function handleConnectionClose()
{
self.emit('socket_close');
self._set_state('offline');
// Prevent additional events from this socket
ws.onopen = ws.onerror = ws.onclose = ws.onmessage = function () {};
// Should we be connected?
if (!self._should_connect) return;
// Delay and retry.
self._retry += 1;
self._retry_timer = setTimeout(function () {
if (self._remote.trace) console.log("server: retry");
if (!self._should_connect) return;
self.connect();
}, self._retry < 40
? 1000/20 // First, for 2 seconds: 20 times per second
: self._retry < 40+60
? 1000 // Then, for 1 minute: once per second
: self._retry < 40+60+60
? 10*1000 // Then, for 10 minutes: once every 10 seconds
: 30*1000); // Then: once every 30 seconds
}
ws.onmessage = function (msg) {
self.emit('message', msg.data);
};
};
Server.prototype.disconnect = function ()
{
this._should_connect = false;
this._set_state('offline');
if (this.ws) {
this.ws.close();
}
};
/**
* Submit a Request object to this server.
*/
Server.prototype.request = function (request)
{
var self = this;
// Only bother if we are still connected.
if (self._ws) {
request.message.id = self._id;
self._requests[request.message.id] = request;
// Advance message ID
self._id++;
if (self._state === "online" ||
(request.message.command === "subscribe" && self._ws.readyState === 1)) {
if (self._remote.trace) {
utils.logObject("server: request: %s", request.message);
}
self._ws.send(JSON.stringify(request.message));
} else {
// XXX There are many ways to make self smarter.
self.once('connect', function () {
if (self._remote.trace) {
utils.logObject("server: request: %s", request.message);
}
self._ws.send(JSON.stringify(request.message));
});
}
} else {
if (self._remote.trace) {
utils.logObject("server: request: DROPPING: %s", request.message);
}
}
};
Server.prototype._set_state = function (state) {
if (state !== this._state) {
this._state = state;
this.emit('state', state);
if (state === 'online') {
this.emit('connect');
} else if (state === 'offline') {
this.emit('disconnect');
}
}
};
Server.prototype._handle_message = function (json) {
var self = this;
var message = JSON.parse(json);
if (message.type === 'response') {
// A response to a request.
var request = self._requests[message.id];
delete self._requests[message.id];
if (!request) {
if (self._remote.trace) utils.logObject("server: UNEXPECTED: %s", message);
} else if ('success' === message.status) {
if (self._remote.trace) utils.logObject("server: response: %s", message);
request.emit('success', message.result);
self.emit('response_'+request.message.command, message.result, request, message);
self._remote.emit('response_'+request.message.command, message.result, request, message);
} else if (message.error) {
if (self._remote.trace) utils.logObject("server: error: %s", message);
request.emit('error', {
'error' : 'remoteError',
'error_message' : 'Remote reported an error.',
'remote' : message
});
}
} else if (message.type === 'serverStatus') {
// This message is only received when online. As we are connected, it is the definative final state.
self._set_state(
Server.online_states.indexOf(message.server_status) !== -1
? 'online'
: 'offline');
}
};
Server.prototype._handle_response_subscribe = function (message)
{
var self = this;
self._server_status = message.server_status;
if (Server.online_states.indexOf(message.server_status) !== -1) {
self._set_state('online');
}
};
exports.Server = Server;
// vim:sw=2:sts=2:ts=8:et

Submodule src/js/sjcl deleted from dbdef434e7

View File

@@ -6,11 +6,9 @@ sjcl.bn.prototype.divRem = function (that) {
var thisa = this.abs(), thata = that.abs(), quot = new this._class(0),
ci = 0;
if (!thisa.greaterEquals(thata)) {
this.initWith(0);
return this;
return [new sjcl.bn(0), this.copy()];
} else if (thisa.equals(thata)) {
this.initWith(1);
return this;
return [new sjcl.bn(1), new sjcl.bn(0)];
}
for (; thisa.greaterEquals(thata); ci++) {
@@ -45,8 +43,8 @@ sjcl.bn.prototype.div = function (that) {
};
sjcl.bn.prototype.sign = function () {
return this.greaterEquals(sjcl.bn.ZERO) ? 1 : -1;
};
return this.greaterEquals(sjcl.bn.ZERO) ? 1 : -1;
};
/** -this */
sjcl.bn.prototype.neg = function () {
@@ -59,3 +57,89 @@ sjcl.bn.prototype.abs = function () {
return this.neg();
} else return this;
};
/** this >> that */
sjcl.bn.prototype.shiftRight = function (that) {
if ("number" !== typeof that) {
throw new Error("shiftRight expects a number");
}
that = +that;
if (that < 0) {
return this.shiftLeft(that);
}
var a = new sjcl.bn(this);
while (that >= this.radix) {
a.limbs.shift();
that -= this.radix;
}
while (that--) {
a.halveM();
}
return a;
};
/** this >> that */
sjcl.bn.prototype.shiftLeft = function (that) {
if ("number" !== typeof that) {
throw new Error("shiftLeft expects a number");
}
that = +that;
if (that < 0) {
return this.shiftRight(that);
}
var a = new sjcl.bn(this);
while (that >= this.radix) {
a.limbs.unshift(0);
that -= this.radix;
}
while (that--) {
a.doubleM();
}
return a;
};
/** (int)this */
// NOTE Truncates to 32-bit integer
sjcl.bn.prototype.toNumber = function () {
return this.limbs[0] | 0;
};
/** find n-th bit, 0 = LSB */
sjcl.bn.prototype.testBit = function (bitIndex) {
var limbIndex = Math.floor(bitIndex / this.radix);
var bitIndexInLimb = bitIndex % this.radix;
if (limbIndex >= this.limbs.length) return 0;
return (this.limbs[limbIndex] >>> bitIndexInLimb) & 1;
};
/** set n-th bit, 0 = LSB */
sjcl.bn.prototype.setBitM = function (bitIndex) {
var limbIndex = Math.floor(bitIndex / this.radix);
var bitIndexInLimb = bitIndex % this.radix;
while (limbIndex >= this.limbs.length) this.limbs.push(0);
this.limbs[limbIndex] |= 1 << bitIndexInLimb;
this.cnormalize();
return this;
};
sjcl.bn.prototype.modInt = function (n) {
return this.toNumber() % n;
};

View File

@@ -0,0 +1,45 @@
sjcl.bn.prototype.jacobi = function (that) {
var a = this;
that = new sjcl.bn(that);
if (that.sign() === -1) return;
// 1. If a = 0 then return(0).
if (a.equals(0)) { return 0; }
// 2. If a = 1 then return(1).
if (a.equals(1)) { return 1; }
var s = 0;
// 3. Write a = 2^e * a1, where a1 is odd.
var e = 0;
while (!a.testBit(e)) e++;
var a1 = a.shiftRight(e);
// 4. If e is even then set s ← 1.
if ((e & 1) === 0) {
s = 1;
} else {
var residue = that.modInt(8);
if (residue === 1 || residue === 7) {
// Otherwise set s ← 1 if n ≡ 1 or 7 (mod 8)
s = 1;
} else if (residue === 3 || residue === 5) {
// Or set s ← 1 if n ≡ 3 or 5 (mod 8).
s = -1;
}
}
// 5. If n ≡ 3 (mod 4) and a1 ≡ 3 (mod 4) then set s ← s.
if (that.modInt(4) === 3 && a1.modInt(4) === 3) {
s = -s;
}
if (a1.equals(1)) {
return s;
} else {
return s * that.mod(a1).jacobi(a1);
}
};

View File

@@ -0,0 +1,156 @@
sjcl.bn.prototype.invDigit = function ()
{
var radixMod = 1 + this.radixMask;
if (this.limbs.length < 1) return 0;
var x = this.limbs[0];
if ((x&1) == 0) return 0;
var y = x&3; // y == 1/x mod 2^2
y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4
y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8
y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16
// last step - calculate inverse mod DV directly;
// assumes 16 < radixMod <= 32 and assumes ability to handle 48-bit ints
y = (y*(2-x*y%radixMod))%radixMod; // y == 1/x mod 2^dbits
// we really want the negative inverse, and -DV < y < DV
return (y>0)?radixMod-y:-y;
};
// returns bit length of the integer x
function nbits(x) {
var r = 1, t;
if((t=x>>>16) != 0) { x = t; r += 16; }
if((t=x>>8) != 0) { x = t; r += 8; }
if((t=x>>4) != 0) { x = t; r += 4; }
if((t=x>>2) != 0) { x = t; r += 2; }
if((t=x>>1) != 0) { x = t; r += 1; }
return r;
}
// JSBN-style add and multiply for SJCL w/ 24 bit radix
sjcl.bn.prototype.am = function (i,x,w,j,c,n) {
var xl = x&0xfff, xh = x>>12;
while (--n >= 0) {
var l = this.limbs[i]&0xfff;
var h = this.limbs[i++]>>12;
var m = xh*l+h*xl;
l = xl*l+((m&0xfff)<<12)+w.limbs[j]+c;
c = (l>>24)+(m>>12)+xh*h;
w.limbs[j++] = l&0xffffff;
}
return c;
}
var Montgomery = function (m)
{
this.m = m;
this.mt = m.limbs.length;
this.mt2 = this.mt * 2;
this.mp = m.invDigit();
this.mpl = this.mp&0x7fff;
this.mph = this.mp>>15;
this.um = (1<<(m.radix-15))-1;
};
Montgomery.prototype.reduce = function (x)
{
var radixMod = x.radixMask + 1;
while (x.limbs.length <= this.mt2) // pad x so am has enough room later
x.limbs[x.limbs.length] = 0;
for (var i = 0; i < this.mt; ++i) {
// faster way of calculating u0 = x[i]*mp mod 2^radix
var j = x.limbs[i]&0x7fff;
var u0 = (j*this.mpl+(((j*this.mph+(x.limbs[i]>>15)*this.mpl)&this.um)<<15))&x.radixMask;
// use am to combine the multiply-shift-add into one call
j = i+this.mt;
x.limbs[j] += this.m.am(0,u0,x,i,0,this.mt);
// propagate carry
while (x.limbs[j] >= radixMod) { x.limbs[j] -= radixMod; x.limbs[++j]++; }
}
x.trim();
x = x.shiftRight(this.mt * this.m.radix);
if (x.greaterEquals(this.m)) x = x.sub(this.m);
return x.trim().normalize().reduce();
};
Montgomery.prototype.square = function (x)
{
return this.reduce(x.square());
};
Montgomery.prototype.multiply = function (x, y)
{
return this.reduce(x.mul(y));
};
Montgomery.prototype.convert = function (x)
{
return x.abs().shiftLeft(this.mt * this.m.radix).mod(this.m);
};
Montgomery.prototype.revert = function (x)
{
return this.reduce(x.copy());
};
sjcl.bn.prototype.powermodMontgomery = function (e, m)
{
var i = e.bitLength(), k, r = new this._class(1);
if (i <= 0) return r;
else if (i < 18) k = 1;
else if (i < 48) k = 3;
else if (i < 144) k = 4;
else if (i < 768) k = 5;
else k = 6;
if (i < 8 || !m.testBit(0)) {
// For small exponents and even moduli, use a simple square-and-multiply
// algorithm.
return this.powermod(e, m);
}
var z = new Montgomery(m);
e.trim().normalize();
// precomputation
var g = new Array(), n = 3, k1 = k-1, km = (1<<k)-1;
g[1] = z.convert(this);
if (k > 1) {
var g2 = z.square(g[1]);
while (n <= km) {
g[n] = z.multiply(g2, g[n-2]);
n += 2;
}
}
var j = e.limbs.length-1, w, is1 = true, r2 = new this._class(), t;
i = nbits(e.limbs[j])-1;
while (j >= 0) {
if (i >= k1) w = (e.limbs[j]>>(i-k1))&km;
else {
w = (e.limbs[j]&((1<<(i+1))-1))<<(k1-i);
if (j > 0) w |= e.limbs[j-1]>>(this.radix+i-k1);
}
n = k;
while ((w&1) == 0) { w >>= 1; --n; }
if ((i -= n) < 0) { i += this.radix; --j; }
if (is1) { // ret == 1, don't bother squaring or multiplying it
r = g[w].copy();
is1 = false;
} else {
while (n > 1) { r2 = z.square(r); r = z.square(r2); n -= 2; }
if (n > 0) r2 = z.square(r); else { t = r; r = r2; r2 = t; }
r = z.multiply(r2,g[w]);
}
while (j >= 0 && (e.limbs[j]&(1<<i)) == 0) {
r2 = z.square(r); t = r; r = r2; r2 = t;
if (--i < 0) { i = this.radix-1; --j; }
}
}
return z.revert(r);
}

View File

@@ -1,6 +1,6 @@
// ----- for secp256k1 ------
// Overwrite NIST-P256 with secp256k1 so we're on even footing
// Overwrite NIST-P256 with secp256k1
sjcl.ecc.curves.c256 = new sjcl.ecc.curve(
sjcl.bn.pseudoMersennePrime(256, [[0,-1],[4,-1],[6,-1],[7,-1],[8,-1],[9,-1],[32,-1]]),
"0x14551231950b75fc4402da1722fc9baee",

View File

@@ -17,7 +17,7 @@ sjcl.ecc.ecdsa.publicKey.prototype = {
l = R.bitLength(),
r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)),
s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)),
sInv = s.modInverse(R),
sInv = s.inverseMod(R),
hG = sjcl.bn.fromBits(hash).mul(sInv).mod(R),
hA = r.mul(sInv).mod(R),
r2 = this._curve.G.mult2(hG, hA, this._point).x;

96
src/js/sjcl/Makefile Normal file
View File

@@ -0,0 +1,96 @@
JSDOCSTYLE= codeview
JSDOCDIR= jsdoc_toolkit-2.3.3-beta
JSDOC= $(JSDOCDIR)/jsrun.sh
JSTEMPLATEDIR= $(JSDOCDIR)/templates/$(JSDOCSTYLE)/
YUICOMPRESSOR= yuicompressor-2.4.2.jar
include config.mk
.PHONY: all test test_yui test_closure test_uncompressed lint compression_stats
all: sjcl.js
sjcl.js: $(COMPRESS)
cp $^ $@
core.js: $(SOURCES) config.mk
cat $(SOURCES) > $@
# compressed targets
core_closure.js: core.js compress/compress_with_closure.sh compress/*.pl
compress/compress_with_closure.sh $< > $@
core_yui.js: core.js compress/compress_with_yui.sh compress/*.pl
compress/compress_with_yui.sh $< > $@
compression_stats: core.js core_closure.js core_yui.js
gzip -c core.js > core.js.gz
gzip -c core_yui.js > core_yui.js.gz
gzip -c core_closure.js > core_closure.js.gz
@echo
@echo
@echo Compression stats:
@echo
@wc -c core.js core_closure.js core_yui.js | head -n -1
@echo
@wc -c core*.js.gz | head -n -1
@echo
@echo
rm -f core*.js.gz
doc: $(SOURCES)
rm -fr $@
JSDOCDIR=$(JSDOCDIR) $(JSDOC) -t=$(JSTEMPLATEDIR) $(SOURCES) -d=$@
doc_private: $(SOURCES)
rm -fr $@
JSDOCDIR=$(JSDOCDIR) $(JSDOC) -t=$(JSTEMPLATEDIR) $(SOURCES) --private -d=$@
lint: core.js core/*.js test/*.js browserTest/*.js lint/coding_guidelines.pl
rhino lint/jslint_rhino.js core.js
lint/coding_guidelines.pl core/*.js test/*.js browserTest/*.js
TEST_COMMON= browserTest/rhinoUtil.js test/test.js
TEST_SCRIPTS= $(TEST_COMMON) \
test/aes_vectors.js test/aes_test.js \
test/ocb2_vectors.js test/ocb2_test.js \
test/ccm_vectors.js test/ccm_test.js \
test/cbc_vectors.js test/cbc_test.js \
test/gcm_vectors.js test/gcm_test.js \
test/sha256_vectors.js test/sha256_test.js \
test/sha256_test_brute_force.js \
test/sha512_vectors.js test/sha512_test.js \
test/sha512_test_brute_force.js \
test/sha1_vectors.js test/sha1_test.js \
test/hmac_vectors.js test/hmac_test.js \
test/pbkdf2_test.js \
test/bn_vectors.js test/bn_test.js \
test/ecdsa_test.js test/ecdsa_vectors.js test/ecdh_test.js
TEST_SCRIPTS_OPT= $(TEST_COMMON) \
test/srp_vectors.js test/srp_test.js
# Rhino fails at -O 0. Probably because the big files full of test vectors blow the
# bytecode limit. So, run most tests with -O -1. But modular exponentiation is
# 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:
find . -name '*~' -delete
rm -f core.js core_*.js
clean: tidy
rm -fr sjcl.js doc doc_private
distclean: clean
./configure
make sjcl.js tidy

View File

@@ -0,0 +1,32 @@
SJCL used to be in the public domain. Now it's:
Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh, Stanford University.
This is for liability reasons. (Speaking of which, SJCL comes with NO
WARRANTY WHATSOEVER, express or implied, to the limit of applicable
law.)
SJCL is dual-licensed under the GNU GPL version 2.0 or higher, and a
2-clause BSD license. You may use SJCL under the terms of either of
these licenses. For your convenience, the GPL versions 2.0 and 3.0
and the 2-clause BSD license are included here. Additionally, you may
serve "crunched" copies of sjcl (i.e. those with comments removed,
and other transformations to reduce code size) without any copyright
notice.
SJCL includes JsDoc toolkit, YUI compressor, Closure compressor,
JSLint and the CodeView template in its build system. These programs'
copyrights are owned by other people. They are distributed here under
the MPL, MIT, BSD, Apache and JSLint licenses. Codeview is "free for
download" but has no license attached; it is Copyright 2010 Wouter Bos.
The BSD license is (almost?) strictly more permissive, but the
additionally licensing under the GPL allows us to use OCB 2.0 code
royalty-free (at least, if OCB 2.0's creator Phil Rogaway has anything
to say about it). Note that if you redistribute SJCL under a license
other than the GPL, you or your users may need to pay patent licensing
fees for OCB 2.0.
There may be patents which apply to SJCL other than Phil Rogaway's OCB
patents. We suggest that you consult legal counsel before using SJCL
in a commercial project.

View File

@@ -0,0 +1,36 @@
SJCL comes with a file sjcl.js pre-built. This default build includes
all the modules except for sjcl.codec.bytes (because the demo site doesn't
use it). All you need to do to install is copy this file to your web
server and start using it.
SJCL is divided into modules implementing various cryptographic and
convenience functions. If you don't need them all for your application,
you can reconfigure SJCL for a smaller code size. To do this, you can
run
./configure --without-all --with-aes --with-sha256 ...
Then type
make
to rebuild sjcl.js. This will also create a few intermediate files
core*.js; you can delete these automatically by typing
make sjcl.js tidy
instead. You will need make, perl, bash and java to rebuild SJCL.
Some of the modules depend on other modules; configure should handle this
automatically unless you tell it --without-FOO --with-BAR, where BAR
depends on FOO. If you do this, configure will yell at you.
SJCL is compressed by stripping comments, shortening variable names, etc.
You can also pass a --compress argument to configure to change the
compressor. By default SJCL uses some perl/sh scripts and Google's
Closure compressor.
If you reconfigure SJCL, it is recommended that you run the included test
suite by typing "make test". If this prints "FAIL" or segfaults, SJCL
doesn't work; please file a bug.

View File

@@ -0,0 +1,30 @@
Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation
are those of the authors and should not be interpreted as representing
official policies, either expressed or implied, of the authors.

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>SJCL browser test</title>
<link rel="stylesheet" type="text/css" href="test.css"/>
<script type="text/javascript" src="browserUtil.js"></script>
<script type="text/javascript" src="../test/run_tests_browser.js"></script>
</head>
<body onload="testCores(['sjcl.js'])">
<h1>SJCL browser test</h1>
<div id="status">Waiting for tests to begin...</div>
<div id="print"></div>
</body>
</html>

View File

@@ -0,0 +1,133 @@
browserUtil = {};
browserUtil.isRhino = (typeof(window) === 'undefined');
/**
* Pause (for the graphics to update and the script timer to clear), then run the
* specified action.
*/
browserUtil.pauseAndThen = function (cb) {
cb && window.setTimeout(cb, 1);
};
/**
* Iterate using continuation-passing style.
*/
browserUtil.cpsIterate = function (f, start, end, pause, callback) {
var pat = pause ? browserUtil.pauseAndThen : function (cb) { cb && cb(); };
function go() {
var called = false;
if (start >= end) {
pat(callback);
} else {
pat(function () { f(start, function () {
if (!called) { called = true; start++; go(); }
}); });
}
}
go (start);
};
/**
* Map a function over an array using continuation-passing style.
*/
browserUtil.cpsMap = function (map, list, pause, callback) {
browserUtil.cpsIterate(function (i, cb) { map(list[i], i, list.length, cb); },
0, list.length, pause, callback);
}
/** Cache for remotely loaded scripts. */
browserUtil.scriptCache = {}
/** Load several scripts, then call back */
browserUtil.loadScripts = function(scriptNames, cbSuccess, cbError) {
var head = document.getElementsByTagName('head')[0];
browserUtil.cpsMap(function (script, i, n, cb) {
var scriptE = document.createElement('script'), xhr, loaded = false;
browserUtil.status("Loading script " + script);
if (window.location.protocol === "file:") {
/* Can't make an AJAX request for files.
* But, we know the load time will be short, so timeout-based error
* detection is fine.
*/
scriptE.onload = function () {
loaded = true;
cb();
};
scriptE.onerror = function(err) {
cbError && cbError(script, err, cb);
};
script.onreadystatechange = function() {
if (this.readyState == 'complete' || this.readyState == 'loaded') {
loaded = true;
cb();
}
};
scriptE.type = 'text/javascript';
scriptE.src = script+"?"+(new Date().valueOf());
window.setTimeout(function () {
loaded || cbError && cbError(script, "timeout expired", cb);
}, 100);
head.appendChild(scriptE);
} else if (browserUtil.scriptCache[script] !== undefined) {
try {
scriptE.appendChild(document.createTextNode(browserUtil.scriptCache[script]));
} catch (e) {
scriptE.text = browserUtil.scriptCache[script];
}
head.appendChild(scriptE);
cb();
} else {
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest;
} else if (window.ActiveXObject) {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
browserUtil.scriptCache[script] = xhr.responseText;
try {
scriptE.appendChild(document.createTextNode(xhr.responseText));
} catch (e) {
scriptE.text = xhr.responseText;
}
head.appendChild(scriptE);
cb();
} else {
cbError && cbError(script, xhr.status, cb);
}
}
}
xhr.open("GET", script+"?"+(new Date().valueOf()), true);
xhr.send();
}
}, scriptNames, false, cbSuccess);
};
/** Write a message to the console */
browserUtil.write = function(type, message) {
var d1 = document.getElementById("print"), d2 = document.createElement("div"), d3 = document.createElement("div");
d3.className = type;
d3.appendChild(document.createTextNode(message));
d2.appendChild(d3);
d1.appendChild(d2);
return { update: function (type2, message2) {
var d4 = document.createElement("div");
d4.className = type2 + " also";
d4.appendChild(document.createTextNode(message2));
d2.insertBefore(d4, d3);
}};
};
/** Write a newline. Does nothing in the browser. */
browserUtil.writeNewline = function () { };
/** Write a message to the status line */
browserUtil.status = function(message) {
var d1 = document.getElementById("status");
d1.replaceChild(document.createTextNode(message), d1.firstChild);
};

View File

@@ -0,0 +1,44 @@
browserUtil = {
isRhino: 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) {
print(message);
return { update: function (type2, message2) {
if (type2 === 'pass') { print(" + " + message2); }
else if (type2 === 'unimplemented') { print(" ? " + message2); }
else { print(" - " + message2); }
}};
},
writeNewline: function () { print(""); },
status: function(message) {}
};

View File

@@ -0,0 +1,59 @@
* {
margin: 0px;
padding: 0px;
font-family: Arial, Helvetica, FreeSans, sans;
}
#print {
position: relative;
width: 40em;
margin: 0px auto;
padding: 5px;
}
#print div {
position: relative;
}
.pass { color: #0A0; }
.fail { color: #A00; }
.unimplemented { color: #F80; }
.begin {
text-align: center;
padding-bottom: 2px;
border-bottom: 1px solid #aaa;
margin: 0px auto 2px auto;
}
.all {
text-align: center;
font-weight: bold;
}
*+* > .begin, *+* > .all {
margin-top: 1em;
}
.also {
float: right;
width: 17em;
text-align: right;
}
h1 {
text-align: center;
background: #8A0000;
padding: 5px;
color: white;
}
#status {
padding: 3px 10px 3px 5px;
background: #d5c490;
color: #444;
font-size: 0.8em;
margin-bottom: 1em;
height: 1.3em;
vertical-align: middle;
}

View File

@@ -0,0 +1,5 @@
{
"name": "sjcl",
"version": "1.0.0",
"main": ["./sjcl.js"]
}

Binary file not shown.

View File

@@ -0,0 +1,15 @@
#!/bin/bash
DIR=`dirname $0`
$DIR/remove_constants.pl $1 | $DIR/opacify.pl > ._tmpRC.js
echo -n '"use strict";'
java -jar $DIR/compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS \
--js ._tmpRC.js \
| $DIR/digitize.pl \
| $DIR/dewindowize.pl
rm -f ._tmpRC.js

View File

@@ -0,0 +1,13 @@
#!/bin/bash
# Compress $1 with YUI Compressor 2.4.2, returning the compressed script on stdout
DIR=`dirname $0`
$DIR/remove_constants.pl $1 > ._tmpRC.js
java -jar $DIR/yuicompressor-2.4.2.jar ._tmpRC.js \
| $DIR/digitize.pl
rm -f ._tmpRC.js

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env perl
while (<>) {
s/window\.sjcl\s*=/var sjcl=/g;
s/window\.sjcl/sjcl/g;
print;
}

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env perl
# Convert numbers to hex, when doing so is likely to increase compressibility.
# This actually makes the script slightly longer, but generally makes it compress
# to something shorter.
#
# Here we're targeting constants like 0xFF, 0xFFFF0000, 0x10101, 0x100000000, etc.
sub digitize {
my $number = shift;
if ($number >= 256) {
my $nn = `printf "%x" $number`;
if ($nn =~ /^[01f]+$/i) { return "0x$nn"; }
}
return $number;
}
while (<>) {
s/([^a-zA-Z0-9_])(\d+)/$1 . digitize $2/eg;
print;
}

39
src/js/sjcl/compress/opacify.pl Executable file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env perl
# This script is a hack.
#
# Opacify all non-private names by turning them into strings.
# That way, the Google compressor won't rename them.
#
# The script ignores properties whose names begin with _, because they
# are believed to be private.
#
# XXX TODO FIXME: this messes with strings, so it screws up exceptions.
my $script = join '', <>;
# remove comments
#$script =~ s=/\*([^\*]|\*+[^\/])*\*/==g;
#$script =~ s=//.*==g;
# stringify property names
$script =~ s=\.([a-zA-Z][_a-zA-Z0-9]*)=['$1']=g;
# destringify 'prototype'
$script =~ s=\['prototype'\]=.prototype=g;
# stringify sjcl
$script =~ s=(?:var\s+)?sjcl(\.|\s*\=)=window['sjcl']$1=g;
# stringify object notation
$script =~ s=([\{,]
\s*
(?:/\*(?:[^\*]|\*+[^\/])*\*/\s* # preserve C-style comments
|//[^\n]*\n\s*)*)
([a-zA-Z0-9][_a-zA-Z0-9]*):=$1'$2':=xg;
# Export sjcl. This is a bit of a hack, and might get replaced later.
print $script;
# not necessary with windowization.
# print "window\['sjcl'\] = sjcl;\n";

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env perl
# This script is a hack. It identifies things which it believes to be
# constant, then replaces them throughout the code.
#
# Constants are identified as properties declared in object notation
# with values consisting only of capital letters and underscores. If
# the first character is an underscore, the constant is private, and
# can be removed entirely.
#
# The script dies if any two constants have the same property name but
# different values.
my $script = join '', <>;
# remove comments
#$script =~ s=/\*([^\*]|\*+[^\/])*\*/==g;
#$script =~ s=//.*==g;
sub preserve {
my $stuff = shift;
$stuff =~ s/,//;
return $stuff;
}
my %constants = ();
sub add_constant {
my ($name, $value) = @_;
if (defined $constants{$name} && $constants{$name} ne $value) {
print STDERR "variant constant $name = $value";
die;
} else {
$constants{$name} = $value;
#print STDERR "constant: $name = $value\n";
}
}
# find private constants
while ($script =~
s/([,\{]) \s* # indicator that this is part of an object
(_[A-Z0-9_]+) \s* : \s* # all-caps variable name beginning with _
(\d+|0x[0-9A-Fa-f]+) \s* # numeric value
([,\}]) # next part of object
/preserve "$1$4"/ex) {
add_constant $2, $3;
}
my $script2 = '';
# find public constants
while ($script =~
s/^(.*?) # beginning of script
([,\{]) \s* # indicator that this is part of an object
([A-Z0-9_]+) \s* : \s* # all-caps variable name
(\d+|0x[0-9A-Fa-f]+) \s* # numeric value
([,\}]) # next part of object([,\{]) \s*
/$5/esx) {
$script2 .= "$1$2$3:$4";
add_constant $3, $4;
}
$script = "$script2$script";
foreach (keys %constants) {
my $value = $constants{$_};
$script =~ s/(?:[a-zA-Z0-9_]+\.)+$_(?=[^a-zA-Z0-9_])/$value/g;
}
print $script;

Binary file not shown.

2
src/js/sjcl/config.mk Normal file
View File

@@ -0,0 +1,2 @@
SOURCES= core/sjcl.js core/aes.js core/bitArray.js core/codecString.js core/codecHex.js core/codecBase64.js core/sha256.js core/ccm.js core/ocb2.js core/gcm.js core/hmac.js core/pbkdf2.js core/random.js core/convenience.js
COMPRESS= core_closure.js

143
src/js/sjcl/configure vendored Executable file
View File

@@ -0,0 +1,143 @@
#!/usr/bin/env perl
use strict;
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 %deps = ('aes'=>'sjcl',
'bitArray'=>'sjcl',
'codecString'=>'bitArray',
'codecHex'=>'bitArray',
'codecBase64'=>'bitArray',
'codecBytes'=>'bitArray',
'sha256'=>'codecString',
'sha512'=>'codecString',
'sha1'=>'codecString',
'ccm'=>'bitArray,aes',
'ocb2'=>'bitArray,aes',
'gcm'=>'bitArray,aes',
'hmac'=>'sha256',
'pbkdf2'=>'hmac',
'srp'=>'sha1,bn,bitArray',
'bn'=>'bitArray,random',
'ecc'=>'bn',
'cbc'=>'bitArray,aes',
'random'=>'sha256,aes',
'convenience'=>'ccm,pbkdf2,random,codecBase64');
my $compress = "closure";
my %enabled = ();
$enabled{$_} = 0 foreach (@targets);
# by default, all but codecBytes, srp, bn
$enabled{$_} = 1 foreach (qw/aes bitArray codecString codecHex codecBase64 sha256 ccm ocb2 gcm hmac pbkdf2 random convenience/);
# argument parsing
while ($arg = shift @ARGV) {
if ($arg =~ /^--?with-all$/) {
foreach (@targets) {
if ($enabled{$_} == 0) {
$enabled{$_} = 1;
}
}
} elsif ($arg =~ /^--?without-all$/) {
foreach (@targets) {
if ($enabled{$_} == 1) {
$enabled{$_} = 0;
}
}
} elsif ($arg =~ /^--?with-(.*)$/) {
$targ = $1;
$targ =~ s/-(.)/uc $1/ge;
if (!defined $deps{$targ}) {
print STDERR "No such target $targ\n";
exit 1;
}
$enabled{$targ} = 2;
} elsif ($arg =~ /^--?without-(.*)$/) {
$targ = $1;
$targ =~ s/-(.)/uc $1/ge;
if (!defined $deps{$targ}) {
print STDERR "No such target $targ\n";
exit 1;
}
$enabled{$targ} = -1;
} elsif ($arg =~ /^--?compress(?:or|ion)?=(none|closure|yui)$/) {
$compress = $1;
} else {
my $targets = join " ", @targets;
$targets =~ s/sjcl //;
$targets =~ s/(.{50})\s+/$1\n /g;
print STDERR <<EOT;
Usage: $0 arguments...
Valid arguments are:
--with-all: by default, include all targets
--without-all: by default, include no targets
--compress=none|closure|yui
--with-TARGET: require TARGET
--without-TARGET: forbid TARGET
--help: show this message
Valid targets are:
$targets
EOT
exit 1 unless $arg =~ /^--?help$/;
exit 0;
}
}
my $config = '';
my $pconfig;
# dependency analysis: forbidden
foreach $i (@targets) {
if ($enabled{$i} > 0) {
foreach $j (split /,/, $deps{$i}) {
if ($enabled{$j} == -1) {
if ($enabled{$i} == 2) {
print STDERR "Conflicting options: $i depends on $j\n";
exit 1;
} else {
$enabled{$i} = -1;
last;
}
}
}
}
}
# reverse
foreach $i (reverse @targets) {
if ($enabled{$i} > 0) {
foreach $j (split /,/, $deps{$i}) {
if ($enabled{$j} < $enabled{$i}) {
$enabled{$j} = $enabled{$i};
}
}
$config = "$i $config";
}
}
open CONFIG, "> config.mk" or die "$!";
($pconfig = $config) =~ s/^sjcl //;
$pconfig =~ s/ /\n /g;
print "Enabled components:\n $pconfig\n";
print "Compression: $compress\n";
$config =~ s=\S+=core/$&.js=g;
print CONFIG "SOURCES= $config\n";
$compress = "core_$compress.js";
$compress = 'core.js' if ($compress eq 'core_none.js');
print CONFIG "COMPRESS= $compress\n";

208
src/js/sjcl/core/aes.js Normal file
View File

@@ -0,0 +1,208 @@
/** @fileOverview Low-level AES implementation.
*
* This file contains a low-level implementation of AES, optimized for
* size and for efficiency on several browsers. It is based on
* OpenSSL's aes_core.c, a public-domain implementation by Vincent
* Rijmen, Antoon Bosselaers and Paulo Barreto.
*
* An older version of this implementation is available in the public
* domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh,
* Stanford University 2008-2010 and BSD-licensed for liability
* reasons.
*
* @author Emily Stark
* @author Mike Hamburg
* @author Dan Boneh
*/
/**
* Schedule out an AES key for both encryption and decryption. This
* is a low-level class. Use a cipher mode to do bulk encryption.
*
* @constructor
* @param {Array} key The key as an array of 4, 6 or 8 words.
*
* @class Advanced Encryption Standard (low-level interface)
*/
sjcl.cipher.aes = function (key) {
if (!this._tables[0][0][0]) {
this._precompute();
}
var i, j, tmp,
encKey, decKey,
sbox = this._tables[0][4], decTable = this._tables[1],
keyLen = key.length, rcon = 1;
if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
throw new sjcl.exception.invalid("invalid aes key size");
}
this._key = [encKey = key.slice(0), decKey = []];
// schedule encryption keys
for (i = keyLen; i < 4 * keyLen + 28; i++) {
tmp = encKey[i-1];
// apply sbox
if (i%keyLen === 0 || (keyLen === 8 && i%keyLen === 4)) {
tmp = sbox[tmp>>>24]<<24 ^ sbox[tmp>>16&255]<<16 ^ sbox[tmp>>8&255]<<8 ^ sbox[tmp&255];
// shift rows and add rcon
if (i%keyLen === 0) {
tmp = tmp<<8 ^ tmp>>>24 ^ rcon<<24;
rcon = rcon<<1 ^ (rcon>>7)*283;
}
}
encKey[i] = encKey[i-keyLen] ^ tmp;
}
// schedule decryption keys
for (j = 0; i; j++, i--) {
tmp = encKey[j&3 ? i : i - 4];
if (i<=4 || j<4) {
decKey[j] = tmp;
} else {
decKey[j] = decTable[0][sbox[tmp>>>24 ]] ^
decTable[1][sbox[tmp>>16 & 255]] ^
decTable[2][sbox[tmp>>8 & 255]] ^
decTable[3][sbox[tmp & 255]];
}
}
};
sjcl.cipher.aes.prototype = {
// public
/* Something like this might appear here eventually
name: "AES",
blockSize: 4,
keySizes: [4,6,8],
*/
/**
* Encrypt an array of 4 big-endian words.
* @param {Array} data The plaintext.
* @return {Array} The ciphertext.
*/
encrypt:function (data) { return this._crypt(data,0); },
/**
* Decrypt an array of 4 big-endian words.
* @param {Array} data The ciphertext.
* @return {Array} The plaintext.
*/
decrypt:function (data) { return this._crypt(data,1); },
/**
* The expanded S-box and inverse S-box tables. These will be computed
* on the client so that we don't have to send them down the wire.
*
* There are two tables, _tables[0] is for encryption and
* _tables[1] is for decryption.
*
* The first 4 sub-tables are the expanded S-box with MixColumns. The
* last (_tables[01][4]) is the S-box itself.
*
* @private
*/
_tables: [[[],[],[],[],[]],[[],[],[],[],[]]],
/**
* Expand the S-box tables.
*
* @private
*/
_precompute: function () {
var encTable = this._tables[0], decTable = this._tables[1],
sbox = encTable[4], sboxInv = decTable[4],
i, x, xInv, d=[], th=[], x2, x4, x8, s, tEnc, tDec;
// Compute double and third tables
for (i = 0; i < 256; i++) {
th[( d[i] = i<<1 ^ (i>>7)*283 )^i]=i;
}
for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
// Compute sbox
s = xInv ^ xInv<<1 ^ xInv<<2 ^ xInv<<3 ^ xInv<<4;
s = s>>8 ^ s&255 ^ 99;
sbox[x] = s;
sboxInv[s] = x;
// Compute MixColumns
x8 = d[x4 = d[x2 = d[x]]];
tDec = x8*0x1010101 ^ x4*0x10001 ^ x2*0x101 ^ x*0x1010100;
tEnc = d[s]*0x101 ^ s*0x1010100;
for (i = 0; i < 4; i++) {
encTable[i][x] = tEnc = tEnc<<24 ^ tEnc>>>8;
decTable[i][s] = tDec = tDec<<24 ^ tDec>>>8;
}
}
// Compactify. Considerable speedup on Firefox.
for (i = 0; i < 5; i++) {
encTable[i] = encTable[i].slice(0);
decTable[i] = decTable[i].slice(0);
}
},
/**
* Encryption and decryption core.
* @param {Array} input Four words to be encrypted or decrypted.
* @param dir The direction, 0 for encrypt and 1 for decrypt.
* @return {Array} The four encrypted or decrypted words.
* @private
*/
_crypt:function (input, dir) {
if (input.length !== 4) {
throw new sjcl.exception.invalid("invalid aes block size");
}
var key = this._key[dir],
// state variables a,b,c,d are loaded with pre-whitened data
a = input[0] ^ key[0],
b = input[dir ? 3 : 1] ^ key[1],
c = input[2] ^ key[2],
d = input[dir ? 1 : 3] ^ key[3],
a2, b2, c2,
nInnerRounds = key.length/4 - 2,
i,
kIndex = 4,
out = [0,0,0,0],
table = this._tables[dir],
// load up the tables
t0 = table[0],
t1 = table[1],
t2 = table[2],
t3 = table[3],
sbox = table[4];
// Inner rounds. Cribbed from OpenSSL.
for (i = 0; i < nInnerRounds; i++) {
a2 = t0[a>>>24] ^ t1[b>>16 & 255] ^ t2[c>>8 & 255] ^ t3[d & 255] ^ key[kIndex];
b2 = t0[b>>>24] ^ t1[c>>16 & 255] ^ t2[d>>8 & 255] ^ t3[a & 255] ^ key[kIndex + 1];
c2 = t0[c>>>24] ^ t1[d>>16 & 255] ^ t2[a>>8 & 255] ^ t3[b & 255] ^ key[kIndex + 2];
d = t0[d>>>24] ^ t1[a>>16 & 255] ^ t2[b>>8 & 255] ^ t3[c & 255] ^ key[kIndex + 3];
kIndex += 4;
a=a2; b=b2; c=c2;
}
// Last round.
for (i = 0; i < 4; i++) {
out[dir ? 3&-i : i] =
sbox[a>>>24 ]<<24 ^
sbox[b>>16 & 255]<<16 ^
sbox[c>>8 & 255]<<8 ^
sbox[d & 255] ^
key[kIndex++];
a2=a; a=b; b=c; c=d; d=a2;
}
return out;
}
};

View File

@@ -0,0 +1,187 @@
/** @fileOverview Arrays of bits, encoded as arrays of Numbers.
*
* @author Emily Stark
* @author Mike Hamburg
* @author Dan Boneh
*/
/** @namespace Arrays of bits, encoded as arrays of Numbers.
*
* @description
* <p>
* These objects are the currency accepted by SJCL's crypto functions.
* </p>
*
* <p>
* Most of our crypto primitives operate on arrays of 4-byte words internally,
* but many of them can take arguments that are not a multiple of 4 bytes.
* This library encodes arrays of bits (whose size need not be a multiple of 8
* bits) as arrays of 32-bit words. The bits are packed, big-endian, into an
* array of words, 32 bits at a time. Since the words are double-precision
* floating point numbers, they fit some extra data. We use this (in a private,
* possibly-changing manner) to encode the number of bits actually present
* in the last word of the array.
* </p>
*
* <p>
* Because bitwise ops clear this out-of-band data, these arrays can be passed
* to ciphers like AES which want arrays of words.
* </p>
*/
sjcl.bitArray = {
/**
* Array slices in units of bits.
* @param {bitArray} a The array to slice.
* @param {Number} bstart The offset to the start of the slice, in bits.
* @param {Number} bend The offset to the end of the slice, in bits. If this is undefined,
* slice until the end of the array.
* @return {bitArray} The requested slice.
*/
bitSlice: function (a, bstart, bend) {
a = sjcl.bitArray._shiftRight(a.slice(bstart/32), 32 - (bstart & 31)).slice(1);
return (bend === undefined) ? a : sjcl.bitArray.clamp(a, bend-bstart);
},
/**
* Extract a number packed into a bit array.
* @param {bitArray} a The array to slice.
* @param {Number} bstart The offset to the start of the slice, in bits.
* @param {Number} length The length of the number to extract.
* @return {Number} The requested slice.
*/
extract: function(a, bstart, blength) {
// FIXME: this Math.floor is not necessary at all, but for some reason
// seems to suppress a bug in the Chromium JIT.
var x, sh = Math.floor((-bstart-blength) & 31);
if ((bstart + blength - 1 ^ bstart) & -32) {
// it crosses a boundary
x = (a[bstart/32|0] << (32 - sh)) ^ (a[bstart/32+1|0] >>> sh);
} else {
// within a single word
x = a[bstart/32|0] >>> sh;
}
return x & ((1<<blength) - 1);
},
/**
* Concatenate two bit arrays.
* @param {bitArray} a1 The first array.
* @param {bitArray} a2 The second array.
* @return {bitArray} The concatenation of a1 and a2.
*/
concat: function (a1, a2) {
if (a1.length === 0 || a2.length === 0) {
return a1.concat(a2);
}
var out, i, last = a1[a1.length-1], shift = sjcl.bitArray.getPartial(last);
if (shift === 32) {
return a1.concat(a2);
} else {
return sjcl.bitArray._shiftRight(a2, shift, last|0, a1.slice(0,a1.length-1));
}
},
/**
* Find the length of an array of bits.
* @param {bitArray} a The array.
* @return {Number} The length of a, in bits.
*/
bitLength: function (a) {
var l = a.length, x;
if (l === 0) { return 0; }
x = a[l - 1];
return (l-1) * 32 + sjcl.bitArray.getPartial(x);
},
/**
* Truncate an array.
* @param {bitArray} a The array.
* @param {Number} len The length to truncate to, in bits.
* @return {bitArray} A new array, truncated to len bits.
*/
clamp: function (a, len) {
if (a.length * 32 < len) { return a; }
a = a.slice(0, Math.ceil(len / 32));
var l = a.length;
len = len & 31;
if (l > 0 && len) {
a[l-1] = sjcl.bitArray.partial(len, a[l-1] & 0x80000000 >> (len-1), 1);
}
return a;
},
/**
* Make a partial word for a bit array.
* @param {Number} len The number of bits in the word.
* @param {Number} x The bits.
* @param {Number} [0] _end Pass 1 if x has already been shifted to the high side.
* @return {Number} The partial word.
*/
partial: function (len, x, _end) {
if (len === 32) { return x; }
return (_end ? x|0 : x << (32-len)) + len * 0x10000000000;
},
/**
* Get the number of bits used by a partial word.
* @param {Number} x The partial word.
* @return {Number} The number of bits used by the partial word.
*/
getPartial: function (x) {
return Math.round(x/0x10000000000) || 32;
},
/**
* Compare two arrays for equality in a predictable amount of time.
* @param {bitArray} a The first array.
* @param {bitArray} b The second array.
* @return {boolean} true if a == b; false otherwise.
*/
equal: function (a, b) {
if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) {
return false;
}
var x = 0, i;
for (i=0; i<a.length; i++) {
x |= a[i]^b[i];
}
return (x === 0);
},
/** Shift an array right.
* @param {bitArray} a The array to shift.
* @param {Number} shift The number of bits to shift.
* @param {Number} [carry=0] A byte to carry in
* @param {bitArray} [out=[]] An array to prepend to the output.
* @private
*/
_shiftRight: function (a, shift, carry, out) {
var i, last2=0, shift2;
if (out === undefined) { out = []; }
for (; shift >= 32; shift -= 32) {
out.push(carry);
carry = 0;
}
if (shift === 0) {
return out.concat(a);
}
for (i=0; i<a.length; i++) {
out.push(carry | a[i]>>>shift);
carry = a[i] << (32-shift);
}
last2 = a.length ? a[a.length-1] : 0;
shift2 = sjcl.bitArray.getPartial(last2);
out.push(sjcl.bitArray.partial(shift+shift2 & 31, (shift + shift2 > 32) ? carry : out.pop(),1));
return out;
},
/** xor a block of 4 words together.
* @private
*/
_xor4: function(x,y) {
return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]];
}
};

552
src/js/sjcl/core/bn.js Normal file
View File

@@ -0,0 +1,552 @@
/**
* @constructor
* Constructs a new bignum from another bignum, a number or a hex string.
*/
sjcl.bn = function(it) {
this.initWith(it);
};
sjcl.bn.prototype = {
radix: 24,
maxMul: 8,
_class: sjcl.bn,
copy: function() {
return new this._class(this);
},
/**
* Initializes this with it, either as a bn, a number, or a hex string.
*/
initWith: function(it) {
var i=0, k, n, l;
switch(typeof it) {
case "object":
this.limbs = it.limbs.slice(0);
break;
case "number":
this.limbs = [it];
this.normalize();
break;
case "string":
it = it.replace(/^0x/, '');
this.limbs = [];
// hack
k = this.radix / 4;
for (i=0; i < it.length; i+=k) {
this.limbs.push(parseInt(it.substring(Math.max(it.length - i - k, 0), it.length - i),16));
}
break;
default:
this.limbs = [0];
}
return this;
},
/**
* Returns true if "this" and "that" are equal. Calls fullReduce().
* Equality test is in constant time.
*/
equals: function(that) {
if (typeof that === "number") { that = new this._class(that); }
var difference = 0, i;
this.fullReduce();
that.fullReduce();
for (i = 0; i < this.limbs.length || i < that.limbs.length; i++) {
difference |= this.getLimb(i) ^ that.getLimb(i);
}
return (difference === 0);
},
/**
* Get the i'th limb of this, zero if i is too large.
*/
getLimb: function(i) {
return (i >= this.limbs.length) ? 0 : this.limbs[i];
},
/**
* Constant time comparison function.
* Returns 1 if this >= that, or zero otherwise.
*/
greaterEquals: function(that) {
if (typeof that === "number") { that = new this._class(that); }
var less = 0, greater = 0, i, a, b;
i = Math.max(this.limbs.length, that.limbs.length) - 1;
for (; i>= 0; i--) {
a = this.getLimb(i);
b = that.getLimb(i);
greater |= (b - a) & ~less;
less |= (a - b) & ~greater;
}
return (greater | ~less) >>> 31;
},
/**
* Convert to a hex string.
*/
toString: function() {
this.fullReduce();
var out="", i, s, l = this.limbs;
for (i=0; i < this.limbs.length; i++) {
s = l[i].toString(16);
while (i < this.limbs.length - 1 && s.length < 6) {
s = "0" + s;
}
out = s + out;
}
return "0x"+out;
},
/** this += that. Does not normalize. */
addM: function(that) {
if (typeof(that) !== "object") { that = new this._class(that); }
var i, l=this.limbs, ll=that.limbs;
for (i=l.length; i<ll.length; i++) {
l[i] = 0;
}
for (i=0; i<ll.length; i++) {
l[i] += ll[i];
}
return this;
},
/** this *= 2. Requires normalized; ends up normalized. */
doubleM: function() {
var i, carry=0, tmp, r=this.radix, m=this.radixMask, l=this.limbs;
for (i=0; i<l.length; i++) {
tmp = l[i];
tmp = tmp+tmp+carry;
l[i] = tmp & m;
carry = tmp >> r;
}
if (carry) {
l.push(carry);
}
return this;
},
/** this /= 2, rounded down. Requires normalized; ends up normalized. */
halveM: function() {
var i, carry=0, tmp, r=this.radix, l=this.limbs;
for (i=l.length-1; i>=0; i--) {
tmp = l[i];
l[i] = (tmp+carry)>>1;
carry = (tmp&1) << r;
}
if (!l[l.length-1]) {
l.pop();
}
return this;
},
/** this -= that. Does not normalize. */
subM: function(that) {
if (typeof(that) !== "object") { that = new this._class(that); }
var i, l=this.limbs, ll=that.limbs;
for (i=l.length; i<ll.length; i++) {
l[i] = 0;
}
for (i=0; i<ll.length; i++) {
l[i] -= ll[i];
}
return this;
},
mod: function(that) {
var neg = !this.greaterEquals(new sjcl.bn(0));
that = new sjcl.bn(that).normalize(); // copy before we begin
var out = new sjcl.bn(this).normalize(), ci=0;
if (neg) out = (new sjcl.bn(0)).subM(out).normalize();
for (; out.greaterEquals(that); ci++) {
that.doubleM();
}
if (neg) out = that.sub(out).normalize();
for (; ci > 0; ci--) {
that.halveM();
if (out.greaterEquals(that)) {
out.subM(that).normalize();
}
}
return out.trim();
},
/** return inverse mod prime p. p must be odd. Binary extended Euclidean algorithm mod p. */
inverseMod: function(p) {
var a = new sjcl.bn(1), b = new sjcl.bn(0), x = new sjcl.bn(this), y = new sjcl.bn(p), tmp, i, nz=1;
if (!(p.limbs[0] & 1)) {
throw (new sjcl.exception.invalid("inverseMod: p must be odd"));
}
// invariant: y is odd
do {
if (x.limbs[0] & 1) {
if (!x.greaterEquals(y)) {
// x < y; swap everything
tmp = x; x = y; y = tmp;
tmp = a; a = b; b = tmp;
}
x.subM(y);
x.normalize();
if (!a.greaterEquals(b)) {
a.addM(p);
}
a.subM(b);
}
// cut everything in half
x.halveM();
if (a.limbs[0] & 1) {
a.addM(p);
}
a.normalize();
a.halveM();
// check for termination: x ?= 0
for (i=nz=0; i<x.limbs.length; i++) {
nz |= x.limbs[i];
}
} while(nz);
if (!y.equals(1)) {
throw (new sjcl.exception.invalid("inverseMod: p and x must be relatively prime"));
}
return b;
},
/** this + that. Does not normalize. */
add: function(that) {
return this.copy().addM(that);
},
/** this - that. Does not normalize. */
sub: function(that) {
return this.copy().subM(that);
},
/** this * that. Normalizes and reduces. */
mul: function(that) {
if (typeof(that) === "number") { that = new this._class(that); }
var i, j, a = this.limbs, b = that.limbs, al = a.length, bl = b.length, out = new this._class(), c = out.limbs, ai, ii=this.maxMul;
for (i=0; i < this.limbs.length + that.limbs.length + 1; i++) {
c[i] = 0;
}
for (i=0; i<al; i++) {
ai = a[i];
for (j=0; j<bl; j++) {
c[i+j] += ai * b[j];
}
if (!--ii) {
ii = this.maxMul;
out.cnormalize();
}
}
return out.cnormalize().reduce();
},
/** this ^ 2. Normalizes and reduces. */
square: function() {
return this.mul(this);
},
/** this ^ n. Uses square-and-multiply. Normalizes and reduces. */
power: function(l) {
if (typeof(l) === "number") {
l = [l];
} else if (l.limbs !== undefined) {
l = l.normalize().limbs;
}
var i, j, out = new this._class(1), pow = this;
for (i=0; i<l.length; i++) {
for (j=0; j<this.radix; j++) {
if (l[i] & (1<<j)) {
out = out.mul(pow);
}
pow = pow.square();
}
}
return out;
},
/** this * that mod N */
mulmod: function(that, N) {
return this.mod(N).mul(that.mod(N)).mod(N);
},
/** this ^ x mod N */
powermod: function(x, N) {
var result = new sjcl.bn(1), a = new sjcl.bn(this), k = new sjcl.bn(x);
while (true) {
if (k.limbs[0] & 1) { result = result.mulmod(a, N); }
k.halveM();
if (k.equals(0)) { break; }
a = a.mulmod(a, N);
}
return result.normalize().reduce();
},
trim: function() {
var l = this.limbs, p;
do {
p = l.pop();
} while (l.length && p === 0);
l.push(p);
return this;
},
/** Reduce mod a modulus. Stubbed for subclassing. */
reduce: function() {
return this;
},
/** Reduce and normalize. */
fullReduce: function() {
return this.normalize();
},
/** Propagate carries. */
normalize: function() {
var carry=0, i, pv = this.placeVal, ipv = this.ipv, l, m, limbs = this.limbs, ll = limbs.length, mask = this.radixMask;
for (i=0; i < ll || (carry !== 0 && carry !== -1); i++) {
l = (limbs[i]||0) + carry;
m = limbs[i] = l & mask;
carry = (l-m)*ipv;
}
if (carry === -1) {
limbs[i-1] -= this.placeVal;
}
return this;
},
/** Constant-time normalize. Does not allocate additional space. */
cnormalize: function() {
var carry=0, i, ipv = this.ipv, l, m, limbs = this.limbs, ll = limbs.length, mask = this.radixMask;
for (i=0; i < ll-1; i++) {
l = limbs[i] + carry;
m = limbs[i] = l & mask;
carry = (l-m)*ipv;
}
limbs[i] += carry;
return this;
},
/** Serialize to a bit array */
toBits: function(len) {
this.fullReduce();
len = len || this.exponent || this.bitLength();
var i = Math.floor((len-1)/24), w=sjcl.bitArray, e = (len + 7 & -8) % this.radix || this.radix,
out = [w.partial(e, this.getLimb(i))];
for (i--; i >= 0; i--) {
out = w.concat(out, [w.partial(Math.min(this.radix,len), this.getLimb(i))]);
len -= this.radix;
}
return out;
},
/** Return the length in bits, rounded up to the nearest byte. */
bitLength: function() {
this.fullReduce();
var out = this.radix * (this.limbs.length - 1),
b = this.limbs[this.limbs.length - 1];
for (; b; b >>>= 1) {
out ++;
}
return out+7 & -8;
}
};
/** @this { sjcl.bn } */
sjcl.bn.fromBits = function(bits) {
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;
words[0] = w.extract(bits, 0, e);
for (; e < l; e += t.radix) {
words.unshift(w.extract(bits, e, t.radix));
}
out.limbs = words;
return out;
};
sjcl.bn.prototype.ipv = 1 / (sjcl.bn.prototype.placeVal = Math.pow(2,sjcl.bn.prototype.radix));
sjcl.bn.prototype.radixMask = (1 << sjcl.bn.prototype.radix) - 1;
/**
* Creates a new subclass of bn, based on reduction modulo a pseudo-Mersenne prime,
* 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) {
/** @constructor */
function p(it) {
this.initWith(it);
/*if (this.limbs[this.modOffset]) {
this.reduce();
}*/
}
var ppr = p.prototype = new sjcl.bn(), i, tmp, mo;
mo = ppr.modOffset = Math.ceil(tmp = exponent / ppr.radix);
ppr.exponent = exponent;
ppr.offset = [];
ppr.factor = [];
ppr.minOffset = mo;
ppr.fullMask = 0;
ppr.fullOffset = [];
ppr.fullFactor = [];
ppr.modulus = p.modulus = new sjcl.bn(Math.pow(2,exponent));
ppr.fullMask = 0|-Math.pow(2, exponent % ppr.radix);
for (i=0; i<coeff.length; i++) {
ppr.offset[i] = Math.floor(coeff[i][0] / ppr.radix - tmp);
ppr.fullOffset[i] = Math.ceil(coeff[i][0] / ppr.radix - tmp);
ppr.factor[i] = coeff[i][1] * Math.pow(1/2, exponent - coeff[i][0] + ppr.offset[i] * ppr.radix);
ppr.fullFactor[i] = coeff[i][1] * Math.pow(1/2, exponent - coeff[i][0] + ppr.fullOffset[i] * ppr.radix);
ppr.modulus.addM(new sjcl.bn(Math.pow(2,coeff[i][0])*coeff[i][1]));
ppr.minOffset = Math.min(ppr.minOffset, -ppr.offset[i]); // conservative
}
ppr._class = p;
ppr.modulus.cnormalize();
/** Approximate reduction mod p. May leave a number which is negative or slightly larger than p.
* @this {sjcl.bn}
*/
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;
i = this.minOffset;
while (limbs.length > mo) {
l = limbs.pop();
ll = limbs.length;
for (k=0; k<ol; k++) {
limbs[ll+off[k]] -= fac[k] * l;
}
i--;
if (!i) {
limbs.push(0);
this.cnormalize();
i = this.minOffset;
}
}
this.cnormalize();
return this;
};
/** @this {sjcl.bn} */
ppr._strongReduce = (ppr.fullMask === -1) ? ppr.reduce : function() {
var limbs = this.limbs, i = limbs.length - 1, k, l;
this.reduce();
if (i === this.modOffset - 1) {
l = limbs[i] & this.fullMask;
limbs[i] -= l;
for (k=0; k<this.fullOffset.length; k++) {
limbs[i+this.fullOffset[k]] -= this.fullFactor[k] * l;
}
this.normalize();
}
};
/** mostly constant-time, very expensive full reduction.
* @this {sjcl.bn}
*/
ppr.fullReduce = function() {
var greater, i;
// massively above the modulus, may be negative
this._strongReduce();
// less than twice the modulus, may be negative
this.addM(this.modulus);
this.addM(this.modulus);
this.normalize();
// probably 2-3x the modulus
this._strongReduce();
// less than the power of 2. still may be more than
// the modulus
// HACK: pad out to this length
for (i=this.limbs.length; i<this.modOffset; i++) {
this.limbs[i] = 0;
}
// constant-time subtract modulus
greater = this.greaterEquals(this.modulus);
for (i=0; i<this.limbs.length; i++) {
this.limbs[i] -= this.modulus.limbs[i] * greater;
}
this.cnormalize();
return this;
};
/** @this {sjcl.bn} */
ppr.inverse = function() {
return (this.power(this.modulus.sub(2)));
};
p.fromBits = sjcl.bn.fromBits;
return p;
};
// a small Mersenne prime
sjcl.bn.prime = {
p127: sjcl.bn.pseudoMersennePrime(127, [[0,-1]]),
// Bernstein's prime for Curve25519
p25519: sjcl.bn.pseudoMersennePrime(255, [[0,-19]]),
// NIST primes
p192: sjcl.bn.pseudoMersennePrime(192, [[0,-1],[64,-1]]),
p224: sjcl.bn.pseudoMersennePrime(224, [[0,1],[96,-1]]),
p256: sjcl.bn.pseudoMersennePrime(256, [[0,-1],[96,1],[192,1],[224,-1]]),
p384: sjcl.bn.pseudoMersennePrime(384, [[0,-1],[32,1],[96,-1],[128,-1]]),
p521: sjcl.bn.pseudoMersennePrime(521, [[0,-1]])
};
sjcl.bn.random = function(modulus, paranoia) {
if (typeof modulus !== "object") { modulus = new sjcl.bn(modulus); }
var words, i, l = modulus.limbs.length, m = modulus.limbs[l-1]+1, out = new sjcl.bn();
while (true) {
// get a sequence whose first digits make sense
do {
words = sjcl.random.randomWords(l, paranoia);
if (words[l-1] < 0) { words[l-1] += 0x100000000; }
} while (Math.floor(words[l-1] / m) === Math.floor(0x100000000 / m));
words[l-1] %= m;
// mask off all the limbs
for (i=0; i<l-1; i++) {
words[i] &= modulus.radixMask;
}
// check the rest of the digitssj
out.limbs = words;
if (!out.greaterEquals(modulus)) {
return out;
}
}
};

115
src/js/sjcl/core/cbc.js Normal file
View File

@@ -0,0 +1,115 @@
/** @fileOverview CBC mode implementation
*
* @author Emily Stark
* @author Mike Hamburg
* @author Dan Boneh
*/
/** @namespace
* Dangerous: CBC mode with PKCS#5 padding.
*
* @author Emily Stark
* @author Mike Hamburg
* @author Dan Boneh
*/
if (sjcl.beware === undefined) {
sjcl.beware = {};
}
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."
] = function() {
sjcl.mode.cbc = {
/** The name of the mode.
* @constant
*/
name: "cbc",
/** Encrypt in CBC mode with PKCS#5 padding.
* @param {Object} prp The block cipher. It must have a block size of 16 bytes.
* @param {bitArray} plaintext The plaintext data.
* @param {bitArray} iv The initialization value.
* @param {bitArray} [adata=[]] The authenticated data. Must be empty.
* @return The encrypted data, an array of bytes.
* @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits, or if any adata is specified.
*/
encrypt: function(prp, plaintext, iv, adata) {
if (adata && adata.length) {
throw new sjcl.exception.invalid("cbc can't authenticate data");
}
if (sjcl.bitArray.bitLength(iv) !== 128) {
throw new sjcl.exception.invalid("cbc iv must be 128 bits");
}
var i,
w = sjcl.bitArray,
xor = w._xor4,
bl = w.bitLength(plaintext),
bp = 0,
output = [];
if (bl&7) {
throw new sjcl.exception.invalid("pkcs#5 padding only works for multiples of a byte");
}
for (i=0; bp+128 <= bl; i+=4, bp+=128) {
/* Encrypt a non-final block */
iv = prp.encrypt(xor(iv, plaintext.slice(i,i+4)));
output.splice(i,0,iv[0],iv[1],iv[2],iv[3]);
}
/* Construct the pad. */
bl = (16 - ((bl >> 3) & 15)) * 0x1010101;
/* Pad and encrypt. */
iv = prp.encrypt(xor(iv,w.concat(plaintext,[bl,bl,bl,bl]).slice(i,i+4)));
output.splice(i,0,iv[0],iv[1],iv[2],iv[3]);
return output;
},
/** Decrypt in CBC mode.
* @param {Object} prp The block cipher. It must have a block size of 16 bytes.
* @param {bitArray} ciphertext The ciphertext data.
* @param {bitArray} iv The initialization value.
* @param {bitArray} [adata=[]] The authenticated data. It must be empty.
* @return The decrypted data, an array of bytes.
* @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits, or if any adata is specified.
* @throws {sjcl.exception.corrupt} if if the message is corrupt.
*/
decrypt: function(prp, ciphertext, iv, adata) {
if (adata && adata.length) {
throw new sjcl.exception.invalid("cbc can't authenticate data");
}
if (sjcl.bitArray.bitLength(iv) !== 128) {
throw new sjcl.exception.invalid("cbc iv must be 128 bits");
}
if ((sjcl.bitArray.bitLength(ciphertext) & 127) || !ciphertext.length) {
throw new sjcl.exception.corrupt("cbc ciphertext must be a positive multiple of the block size");
}
var i,
w = sjcl.bitArray,
xor = w._xor4,
bi, bo,
output = [];
adata = adata || [];
for (i=0; i<ciphertext.length; i+=4) {
bi = ciphertext.slice(i,i+4);
bo = xor(iv,prp.decrypt(bi));
output.splice(i,0,bo[0],bo[1],bo[2],bo[3]);
iv = bi;
}
/* check and remove the pad */
bi = output[i-1] & 255;
if (bi == 0 || bi > 16) {
throw new sjcl.exception.corrupt("pkcs#5 padding corrupt");
}
bo = bi * 0x1010101;
if (!w.equal(w.bitSlice([bo,bo,bo,bo], 0, bi*8),
w.bitSlice(output, output.length*32 - bi*8, output.length*32))) {
throw new sjcl.exception.corrupt("pkcs#5 padding corrupt");
}
return w.bitSlice(output, 0, output.length*32 - bi*8);
}
};
};

185
src/js/sjcl/core/ccm.js Normal file
View File

@@ -0,0 +1,185 @@
/** @fileOverview CCM mode implementation.
*
* Special thanks to Roy Nicholson for pointing out a bug in our
* implementation.
*
* @author Emily Stark
* @author Mike Hamburg
* @author Dan Boneh
*/
/** @namespace CTR mode with CBC MAC. */
sjcl.mode.ccm = {
/** The name of the mode.
* @constant
*/
name: "ccm",
/** Encrypt in CCM mode.
* @static
* @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes.
* @param {bitArray} plaintext The plaintext data.
* @param {bitArray} iv The initialization value.
* @param {bitArray} [adata=[]] The authenticated data.
* @param {Number} [tlen=64] the desired tag length, in bits.
* @return {bitArray} The encrypted data, an array of bytes.
*/
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;
tlen = tlen || 64;
adata = adata || [];
if (ivl < 7) {
throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");
}
// compute the length of the length
for (L=2; L<4 && ol >>> 8*L; L++) {}
if (L < 15 - ivl) { L = 15-ivl; }
iv = w.clamp(iv,8*(15-L));
// compute the tag
tag = sjcl.mode.ccm._computeTag(prf, plaintext, iv, adata, tlen, L);
// encrypt
out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L);
return w.concat(out.data, out.tag);
},
/** Decrypt in CCM mode.
* @static
* @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes.
* @param {bitArray} ciphertext The ciphertext data.
* @param {bitArray} iv The initialization value.
* @param {bitArray} [[]] adata The authenticated data.
* @param {Number} [64] tlen the desired tag length, in bits.
* @return {bitArray} The decrypted data.
*/
decrypt: function(prf, ciphertext, iv, adata, tlen) {
tlen = tlen || 64;
adata = adata || [];
var L, i,
w=sjcl.bitArray,
ivl = w.bitLength(iv) / 8,
ol = w.bitLength(ciphertext),
out = w.clamp(ciphertext, ol - tlen),
tag = w.bitSlice(ciphertext, ol - tlen), tag2;
ol = (ol - tlen) / 8;
if (ivl < 7) {
throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");
}
// compute the length of the length
for (L=2; L<4 && ol >>> 8*L; L++) {}
if (L < 15 - ivl) { L = 15-ivl; }
iv = w.clamp(iv,8*(15-L));
// decrypt
out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L);
// check the tag
tag2 = sjcl.mode.ccm._computeTag(prf, out.data, iv, adata, tlen, L);
if (!w.equal(out.tag, tag2)) {
throw new sjcl.exception.corrupt("ccm: tag doesn't match");
}
return out.data;
},
/* Compute the (unencrypted) authentication tag, according to the CCM specification
* @param {Object} prf The pseudorandom function.
* @param {bitArray} plaintext The plaintext data.
* @param {bitArray} iv The initialization value.
* @param {bitArray} adata The authenticated data.
* @param {Number} tlen the desired tag length, in bits.
* @return {bitArray} The tag, but not yet encrypted.
* @private
*/
_computeTag: function(prf, plaintext, iv, adata, tlen, L) {
// compute B[0]
var q, mac, field = 0, offset = 24, tmp, i, macData = [], w=sjcl.bitArray, xor = w._xor4;
tlen /= 8;
// check tag length and message length
if (tlen % 2 || tlen < 4 || tlen > 16) {
throw new sjcl.exception.invalid("ccm: invalid tag length");
}
if (adata.length > 0xFFFFFFFF || plaintext.length > 0xFFFFFFFF) {
// I don't want to deal with extracting high words from doubles.
throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data");
}
// mac the flags
mac = [w.partial(8, (adata.length ? 1<<6 : 0) | (tlen-2) << 2 | L-1)];
// mac the iv and length
mac = w.concat(mac, iv);
mac[3] |= w.bitLength(plaintext)/8;
mac = prf.encrypt(mac);
if (adata.length) {
// mac the associated data. start with its length...
tmp = w.bitLength(adata)/8;
if (tmp <= 0xFEFF) {
macData = [w.partial(16, tmp)];
} else if (tmp <= 0xFFFFFFFF) {
macData = w.concat([w.partial(16,0xFFFE)], [tmp]);
} // else ...
// mac the data itself
macData = w.concat(macData, adata);
for (i=0; i<macData.length; i += 4) {
mac = prf.encrypt(xor(mac, macData.slice(i,i+4).concat([0,0,0])));
}
}
// mac the plaintext
for (i=0; i<plaintext.length; i+=4) {
mac = prf.encrypt(xor(mac, plaintext.slice(i,i+4).concat([0,0,0])));
}
return w.clamp(mac, tlen * 8);
},
/** CCM CTR mode.
* Encrypt or decrypt data and tag with the prf in CCM-style CTR mode.
* May mutate its arguments.
* @param {Object} prf The PRF.
* @param {bitArray} data The data to be encrypted or decrypted.
* @param {bitArray} iv The initialization vector.
* @param {bitArray} tag The authentication tag.
* @param {Number} tlen The length of th etag, in bits.
* @param {Number} L The CCM L value.
* @return {Object} An object with data and tag, the en/decryption of data and tag values.
* @private
*/
_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);
// start the ctr
ctr = w.concat([w.partial(8,L-1)],iv).concat([0,0,0]).slice(0,4);
// en/decrypt the tag
tag = w.bitSlice(xor(tag,prf.encrypt(ctr)), 0, tlen);
// en/decrypt the data
if (!l) { return {tag:tag, data:[]}; }
for (i=0; i<l; i+=4) {
ctr[3]++;
enc = prf.encrypt(ctr);
data[i] ^= enc[0];
data[i+1] ^= enc[1];
data[i+2] ^= enc[2];
data[i+3] ^= enc[3];
}
return { tag:tag, data:w.clamp(data,bl) };
}
};

View File

@@ -0,0 +1,63 @@
/** @fileOverview Bit array codec implementations.
*
* @author Emily Stark
* @author Mike Hamburg
* @author Dan Boneh
*/
/** @namespace Base64 encoding/decoding */
sjcl.codec.base64 = {
/** The base64 alphabet.
* @private
*/
_chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
/** Convert from a bitArray to a base64 string. */
fromBits: function (arr, _noEquals, _url) {
var out = "", i, bits=0, c = sjcl.codec.base64._chars, ta=0, bl = sjcl.bitArray.bitLength(arr);
if (_url) c = c.substr(0,62) + '-_';
for (i=0; out.length * 6 < bl; ) {
out += c.charAt((ta ^ arr[i]>>>bits) >>> 26);
if (bits < 6) {
ta = arr[i] << (6-bits);
bits += 26;
i++;
} else {
ta <<= 6;
bits -= 6;
}
}
while ((out.length & 3) && !_noEquals) { out += "="; }
return out;
},
/** Convert from a base64 string to a bitArray */
toBits: function(str, _url) {
str = str.replace(/\s|=/g,'');
var out = [], i, bits=0, c = sjcl.codec.base64._chars, ta=0, x;
if (_url) c = c.substr(0,62) + '-_';
for (i=0; i<str.length; i++) {
x = c.indexOf(str.charAt(i));
if (x < 0) {
throw new sjcl.exception.invalid("this isn't base64!");
}
if (bits > 26) {
bits -= 26;
out.push(ta ^ x>>>bits);
ta = x << (32-bits);
} else {
bits += 6;
ta ^= x << (32-bits);
}
}
if (bits&56) {
out.push(sjcl.bitArray.partial(bits&56, ta, 1));
}
return out;
}
};
sjcl.codec.base64url = {
fromBits: function (arr) { return sjcl.codec.base64.fromBits(arr,1,1); },
toBits: function (str) { return sjcl.codec.base64.toBits(str,1); }
};

Some files were not shown because too many files have changed in this diff Show More