From 09e8c6e184d6501a0c2c59d73ca64db5c0d3eb95 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Wed, 11 Jan 2012 10:41:52 +0100 Subject: [PATCH] Added comments. --- src/address.js | 8 +++ src/ecdsa.js | 58 ++++++++++++++------- src/eckey.js | 6 +++ src/paillier.js | 6 +++ src/script.js | 127 +++++++++++++++++++++++++++++++++++++++++++-- src/transaction.js | 94 +++++++++++++++++++++++++++++++-- src/util.js | 61 ++++++++++++++++++++-- src/wallet.js | 65 ++++++++++++++++++++++- 8 files changed, 392 insertions(+), 33 deletions(-) diff --git a/src/address.js b/src/address.js index 0941f58..3013764 100644 --- a/src/address.js +++ b/src/address.js @@ -7,6 +7,11 @@ Bitcoin.Address = function (bytes) { this.version = 0x00; }; +/** + * Serialize this object as a standard Bitcoin address. + * + * Returns the address as a base58-encoded string in the standardized format. + */ Bitcoin.Address.prototype.toString = function () { // Get a copy of the hash var hash = this.hash.slice(0); @@ -25,6 +30,9 @@ Bitcoin.Address.prototype.getHashBase64 = function () { return Crypto.util.bytesToBase64(this.hash); }; +/** + * Parse a Bitcoin address contained in a string. + */ Bitcoin.Address.decodeString = function (string) { var bytes = Bitcoin.Base58.decode(string); diff --git a/src/ecdsa.js b/src/ecdsa.js index e986a48..1bfd5d6 100644 --- a/src/ecdsa.js +++ b/src/ecdsa.js @@ -134,6 +134,11 @@ ECPointFp.prototype.isOnCurve = function () { return lhs.equals(rhs); }; +/** + * Validate an elliptic curve point. + * + * See SEC 1, section 3.2.2.1: Elliptic Curve Public Key Validation Primitive + */ ECPointFp.prototype.validate = function () { var n = this.curve.getQ(); @@ -228,25 +233,6 @@ Bitcoin.ECDSA = (function () { return ECDSA.serializeSig(r, s); }, - serializeSig: function (r, s) { - var rBa = r.toByteArrayUnsigned(); - var sBa = s.toByteArrayUnsigned(); - - var sequence = []; - sequence.push(0x02); // INTEGER - sequence.push(rBa.length); - sequence = sequence.concat(rBa); - - sequence.push(0x02); // INTEGER - sequence.push(sBa.length); - sequence = sequence.concat(sBa); - - sequence.unshift(sequence.length); - sequence.unshift(0x30) // SEQUENCE - - return sequence; - }, - verify: function (hash, sig, pubkey) { var obj = ECDSA.parseSig(sig); var r = obj.r; @@ -278,6 +264,40 @@ Bitcoin.ECDSA = (function () { return v.equals(r); }, + /** + * Serialize a signature into DER format. + * + * Takes two BigIntegers representing r and s and returns a byte array. + */ + serializeSig: function (r, s) { + var rBa = r.toByteArrayUnsigned(); + var sBa = s.toByteArrayUnsigned(); + + var sequence = []; + sequence.push(0x02); // INTEGER + sequence.push(rBa.length); + sequence = sequence.concat(rBa); + + sequence.push(0x02); // INTEGER + sequence.push(sBa.length); + sequence = sequence.concat(sBa); + + sequence.unshift(sequence.length); + sequence.unshift(0x30); // SEQUENCE + + return sequence; + }, + + /** + * Parses a byte array containing a DER-encoded signature. + * + * This function will return an object of the form: + * + * { + * r: BigInteger, + * s: BigInteger + * } + */ parseSig: function (sig) { var cursor; if (sig[0] != 0x30) diff --git a/src/eckey.js b/src/eckey.js index d287b51..8088f1f 100644 --- a/src/eckey.js +++ b/src/eckey.js @@ -26,6 +26,12 @@ Bitcoin.ECKey = (function () { return this.pub = ecparams.getG().multiply(this.priv).getEncoded(); }; + /** + * Get the pubKeyHash for this key. + * + * This is calculated as RIPE160(SHA256([encoded pubkey])) and returned as + * a byte array. + */ ECKey.prototype.getPubKeyHash = function () { if (this.pubKeyHash) return this.pubKeyHash; diff --git a/src/paillier.js b/src/paillier.js index ed0ccc9..54881ba 100644 --- a/src/paillier.js +++ b/src/paillier.js @@ -1,3 +1,9 @@ +/** + * Implement the Paillier cryptosystem in JavaScript. + * + * Paillier is useful for multiparty calculation. It is not currently part of any + * BitcoinJS-lib distribution, but it is included here for experimental use. + */ Bitcoin.Paillier = (function () { var rng = new SecureRandom(); var TWO = BigInteger.valueOf(2); diff --git a/src/script.js b/src/script.js index b06fc1a..14140be 100644 --- a/src/script.js +++ b/src/script.js @@ -22,6 +22,17 @@ this.parse(); }; + /** + * Update the parsed script representation. + * + * Each Script object stores the script in two formats. First as a raw byte + * array and second as an array of "chunks", such as opcodes and pieces of + * data. + * + * This method updates the chunks cache. Normally this is called by the + * constructor and you don't need to worry about it. However, if you change + * the script buffer manually, you should update the chunks using this method. + */ Script.prototype.parse = function () { var self = this; @@ -65,6 +76,24 @@ } }; + /** + * Compare the script to known templates of scriptPubKey. + * + * This method will compare the script to a small number of standard script + * templates and return a string naming the detected type. + * + * Currently supported are: + * Address: + * Paying to a Bitcoin address which is the hash of a pubkey. + * OP_DUP OP_HASH160 [pubKeyHash] OP_EQUALVERIFY OP_CHECKSIG + * + * Pubkey: + * Paying to a public key directly. + * [pubKey] OP_CHECKSIG + * + * Strange: + * Any other script (no template matched). + */ Script.prototype.getOutType = function () { if (this.chunks.length == 5 && @@ -85,7 +114,19 @@ } }; - Script.prototype.simpleOutPubKeyHash = function () + /** + * Returns the affected address hash for this output. + * + * For standard transactions, this will return the hash of the pubKey that + * can spend this output. + * + * In the future, for payToScriptHash outputs, this will return the + * scriptHash. Note that non-standard and standard payToScriptHash transactions + * look the same + * + * This method is useful for indexing transactions. + */ + Script.prototype.simpleOutHash = function () { switch (this.getOutType()) { case 'Address': @@ -97,22 +138,64 @@ } }; + /** + * Old name for Script#simpleOutHash. + * + * @deprecated + */ + Script.prototype.simpleOutPubKeyHash = Script.prototype.simpleOutHash; + + /** + * Compare the script to known templates of scriptSig. + * + * This method will compare the script to a small number of standard script + * templates and return a string naming the detected type. + * + * WARNING: Use this method with caution. It merely represents a heuristic + * based on common transaction formats. A non-standard transaction could + * very easily match one of these templates by accident. + * + * Currently supported are: + * Address: + * Paying to a Bitcoin address which is the hash of a pubkey. + * [sig] [pubKey] + * + * Pubkey: + * Paying to a public key directly. + * [sig] + * + * Strange: + * Any other script (no template matched). + */ Script.prototype.getInType = function () { if (this.chunks.length == 1 && Bitcoin.Util.isArray(this.chunks[0])) { - // Direct IP to IP transactions only have the public key in their scriptSig. - // TODO: We could also check that the length of the data is 65 or 33. + // Direct IP to IP transactions only have the signature in their scriptSig. + // TODO: We could also check that the length of the data is correct. return 'Pubkey'; } else if (this.chunks.length == 2 && Bitcoin.Util.isArray(this.chunks[0]) && Bitcoin.Util.isArray(this.chunks[1])) { return 'Address'; } else { - throw new Error("Encountered non-standard scriptSig"); + return 'Strange'; } }; + /** + * Returns the affected public key for this input. + * + * This currently only works with payToPubKeyHash transactions. It will also + * work in the future for standard payToScriptHash transactions that use a + * single public key. + * + * However for multi-key and other complex transactions, this will only return + * one of the keys or raise an error. Therefore, it is recommended for indexing + * purposes to use Script#simpleInHash or Script#simpleOutHash instead. + * + * @deprecated + */ Script.prototype.simpleInPubKey = function () { switch (this.getInType()) { @@ -127,17 +210,45 @@ } }; - Script.prototype.simpleInPubKeyHash = function () + /** + * Returns the affected address hash for this input. + * + * For standard transactions, this will return the hash of the pubKey that + * can spend this output. + * + * In the future, for standard payToScriptHash inputs, this will return the + * scriptHash. + * + * Note: This function provided for convenience. If you have the corresponding + * scriptPubKey available, you are urged to use Script#simpleOutHash instead + * as it is more reliable for non-standard payToScriptHash transactions. + * + * This method is useful for indexing transactions. + */ + Script.prototype.simpleInHash = function () { return Bitcoin.Util.sha256ripe160(this.simpleInPubKey()); }; + /** + * Old name for Script#simpleInHash. + * + * @deprecated + */ + Script.prototype.simpleInPubKeyHash = Script.prototype.simpleInHash; + + /** + * Add an op code to the script. + */ Script.prototype.writeOp = function (opcode) { this.buffer.push(opcode); this.chunks.push(opcode); }; + /** + * Add a data chunk to the script. + */ Script.prototype.writeBytes = function (data) { if (data.length < OP_PUSHDATA1) { @@ -160,6 +271,9 @@ this.chunks.push(data); }; + /** + * Create a standard payToPubKeyHash output. + */ Script.createOutputScript = function (address) { var script = new Script(); @@ -171,6 +285,9 @@ return script; }; + /** + * Create a standard payToPubKeyHash input. + */ Script.createInputScript = function (signature, pubKey) { var script = new Script(); diff --git a/src/transaction.js b/src/transaction.js index ff89a6e..c4c876d 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -28,6 +28,12 @@ } }; + /** + * Turn transaction data into Transaction objects. + * + * Takes an array of plain JavaScript objects containing transaction data and + * returns an array of Transaction objects. + */ Transaction.objectify = function (txs) { var objs = []; for (var i = 0; i < txs.length; i++) { @@ -36,6 +42,16 @@ return objs; }; + /** + * Create a new txin. + * + * Can be called with an existing TransactionIn object to add it to the + * transaction. Or it can be called with a Transaction object and an integer + * output index, in which case a new TransactionIn object pointing to the + * referenced output will be created. + * + * Note that this method does not sign the created input. + */ Transaction.prototype.addInput = function (tx, outIndex) { if (arguments[0] instanceof TransactionIn) { this.ins.push(arguments[0]); @@ -51,6 +67,14 @@ } }; + /** + * Create a new txout. + * + * Can be called with an existing TransactionOut object to add it to the + * transaction. Or it can be called with an Address object and a BigInteger + * for the amount, in which case a new TransactionOut object with those + * values will be created. + */ Transaction.prototype.addOutput = function (address, value) { if (arguments[0] instanceof TransactionOut) { this.outs.push(arguments[0]); @@ -69,6 +93,13 @@ } }; + /** + * Serialize this transaction. + * + * Returns the transaction as a byte array in the standard Bitcoin binary + * format. This method is byte-perfect, i.e. the resulting byte array can + * be hashed to get the transaction's standard Bitcoin hash. + */ Transaction.prototype.serialize = function () { var buffer = []; @@ -103,12 +134,22 @@ var SIGHASH_SINGLE = 3; var SIGHASH_ANYONECANPAY = 80; - Transaction.prototype.hashTransactionForSignature = function (connectedScript, inIndex, hashType) + /** + * Hash transaction for signing a specific input. + * + * Bitcoin uses a different hash for each signed transaction input. This + * method copies the transaction, makes the necessary changes based on the + * hashType, serializes and finally hashes the result. This hash can then be + * used to sign the transaction input in question. + */ + Transaction.prototype.hashTransactionForSignature = + function (connectedScript, inIndex, hashType) { var txTmp = this.clone(); // In case concatenating two scripts ends up with two codeseparators, - // or an extra one at the end, this prevents all those possible incompatibilities. + // or an extra one at the end, this prevents all those possible + // incompatibilities. /*scriptCode = scriptCode.filter(function (val) { return val !== OP_CODESEPARATOR; });*/ @@ -151,12 +192,18 @@ return Crypto.SHA256(hash1, {asBytes: true}); }; + /** + * Calculate and return the transaction's hash. + */ Transaction.prototype.getHash = function () { var buffer = this.serialize(); return Crypto.SHA256(Crypto.SHA256(buffer, {asBytes: true}), {asBytes: true}); }; + /** + * Create a copy of this transaction object. + */ Transaction.prototype.clone = function () { var newTx = new Transaction(); @@ -175,6 +222,28 @@ /** * Analyze how this transaction affects a wallet. + * + * Returns an object with properties 'impact', 'type' and 'addr'. + * + * 'impact' is an object, see Transaction#calcImpact. + * + * 'type' can be one of the following: + * + * recv: + * This is an incoming transaction, the wallet received money. + * 'addr' contains the first address in the wallet that receives money + * from this transaction. + * + * self: + * This is an internal transaction, money was sent within the wallet. + * 'addr' is undefined. + * + * sent: + * This is an outgoing transaction, money was sent out from the wallet. + * 'addr' contains the first external address, i.e. the recipient. + * + * other: + * This method was unable to detect what the transaction does. Either it */ Transaction.prototype.analyze = function (wallet) { if (!(wallet instanceof Bitcoin.Wallet)) return null; @@ -217,6 +286,9 @@ analysis.type = 'self'; } else if (allFromMe) { analysis.type = 'sent'; + // TODO: Right now, firstRecvHash is the first output, which - if the + // transaction was not generated by this library could be the + // change address. analysis.addr = new Bitcoin.Address(firstRecvHash); } else { analysis.type = "other"; @@ -225,6 +297,12 @@ return analysis; }; + /** + * Get a human-readable version of the data returned by Transaction#analyze. + * + * This is merely a convenience function. Clients should consider implementing + * this themselves based on their UI, I18N, etc. + */ Transaction.prototype.getDescription = function (wallet) { var analysis = this.analyze(wallet); @@ -249,7 +327,10 @@ } }; - Transaction.prototype.getTotalValue = function () { + /** + * Get the total amount of a transaction's outputs. + */ + Transaction.prototype.getTotalOutValue = function () { var totalValue = BigInteger.ZERO; for (var j = 0; j < this.outs.length; j++) { var txout = this.outs[j]; @@ -258,6 +339,13 @@ return totalValue; }; + /** + * Old name for Transaction#getTotalOutValue. + * + * @deprecated + */ + Transaction.prototype.getTotalValue = Transaction.prototype.getTotalOutValue; + /** * Calculates the impact a transaction has on this wallet. * diff --git a/src/util.js b/src/util.js index 14bf9cb..0fda4c5 100644 --- a/src/util.js +++ b/src/util.js @@ -38,10 +38,19 @@ for (var i = 0; i < names.length; ++i) // Bitcoin utility functions Bitcoin.Util = { - isArray: Array.isArray || function(o) { + /** + * Cross-browser compatibility version of Array.isArray. + */ + isArray: Array.isArray || function(o) + { return Object.prototype.toString.call(o) === '[object Array]'; }, - makeFilledArray: function (len, val) { + + /** + * Create an array of a certain length filled with a specific value. + */ + makeFilledArray: function (len, val) + { var array = []; var i = 0; while (i < len) { @@ -49,8 +58,16 @@ Bitcoin.Util = { } return array; }, - numToVarInt: function (i) { - // TODO: THIS IS TOTALLY UNTESTED! + + /** + * Turn an integer into a "var_int". + * + * "var_int" is a variable length integer used by Bitcoin's binary format. + * + * Returns a byte array. + */ + numToVarInt: function (i) + { if (i < 0xfd) { // unsigned char return [i]; @@ -65,12 +82,30 @@ Bitcoin.Util = { return [0xff].concat(Crypto.util.wordsToBytes([i >>> 32, i])); } }, - valueToBigInt: function (valueBuffer) { + + /** + * Parse a Bitcoin value byte array, returning a BigInteger. + */ + valueToBigInt: function (valueBuffer) + { if (valueBuffer instanceof BigInteger) return valueBuffer; // Prepend zero byte to prevent interpretation as negative integer return BigInteger.fromByteArrayUnsigned(valueBuffer); }, + + /** + * Format a Bitcoin value as a string. + * + * Takes a BigInteger or byte-array and returns that amount of Bitcoins in a + * nice standard formatting. + * + * Examples: + * 12.3555 + * 0.1234 + * 900.99998888 + * 34.00 + */ formatValue: function (valueBuffer) { var value = this.valueToBigInt(valueBuffer).toString(); var integerPart = value.length > 8 ? value.substr(0, value.length-8) : '0'; @@ -80,7 +115,16 @@ Bitcoin.Util = { while (decimalPart.length < 2) decimalPart += "0"; return integerPart+"."+decimalPart; }, + + /** + * Parse a floating point string as a Bitcoin value. + * + * Keep in mind that parsing user input is messy. You should always display + * the parsed value back to the user to make sure we understood his input + * correctly. + */ parseValue: function (valueString) { + // TODO: Detect other number formats (e.g. comma as decimal separator) var valueComp = valueString.split('.'); var integralPart = valueComp[0]; var fractionalPart = valueComp[1] || "0"; @@ -91,6 +135,13 @@ Bitcoin.Util = { value = value.add(BigInteger.valueOf(parseInt(fractionalPart))); return value; }, + + /** + * Calculate RIPEMD160(SHA256(data)). + * + * Takes an arbitrary byte array as inputs and returns the hash as a byte + * array. + */ sha256ripe160: function (data) { return Crypto.RIPEMD160(Crypto.SHA256(data, {asBytes: true}), {asBytes: true}); } diff --git a/src/wallet.js b/src/wallet.js index 457ebf1..3d12f60 100755 --- a/src/wallet.js +++ b/src/wallet.js @@ -5,7 +5,15 @@ Bitcoin.Wallet = (function () { var Wallet = function () { // Keychain + // + // The keychain is stored as a var in this closure to make accidental + // serialization less likely. + // + // Any functions accessing this value therefore have to be defined in + // the closure of this constructor. var keys = []; + + // Public hashes of our keys this.addressHashes = []; // Transaction data @@ -15,6 +23,13 @@ Bitcoin.Wallet = (function () { // Other fields this.addressPointer = 0; + /** + * Add a key to the keychain. + * + * The corresponding public key can be provided as a second parameter. This + * adds it to the cache in the ECKey object and avoid the need to + * expensively calculate it later. + */ this.addKey = function (key, pub) { if (!(key instanceof Bitcoin.ECKey)) { key = new Bitcoin.ECKey(key); @@ -31,6 +46,9 @@ Bitcoin.Wallet = (function () { this.addressHashes.push(key.getBitcoinAddress().getHashBase64()); }; + /** + * Add multiple keys at once. + */ this.addKeys = function (keys, pubs) { if ("string" === typeof keys) { keys = keys.split(','); @@ -50,6 +68,11 @@ Bitcoin.Wallet = (function () { } }; + /** + * Get the key chain. + * + * Returns an array of base64-encoded private values. + */ this.getKeys = function () { var serializedWallet = []; @@ -60,6 +83,11 @@ Bitcoin.Wallet = (function () { return serializedWallet; }; + /** + * Get the public keys. + * + * Returns an array of base64-encoded public keys. + */ this.getPubKeys = function () { var pubs = []; @@ -70,14 +98,25 @@ Bitcoin.Wallet = (function () { return pubs; }; + /** + * Delete all keys. + */ this.clear = function () { keys = []; }; + /** + * Return the number of keys in this wallet. + */ this.getLength = function () { return keys.length; }; + /** + * Get the addresses for this wallet. + * + * Returns an array of Address objects. + */ this.getAllAddresses = function () { var addresses = []; for (var i = 0; i < keys.length; i++) { @@ -94,14 +133,26 @@ Bitcoin.Wallet = (function () { } }; + /** + * Go to the next address. + * + * If there are no more new addresses available, one will be generated + * automatically. + */ this.getNextAddress = function () { this.addressPointer++; - if(!keys[this.addressPointer]) { + if (!keys[this.addressPointer]) { this.generateAddress(); } return keys[this.addressPointer].getBitcoinAddress(); }; + /** + * Sign a hash with a key. + * + * This method expects the pubKeyHash as the first parameter and the hash + * to be signed as the second parameter. + */ this.signWithKey = function (pubKeyHash, hash) { pubKeyHash = Crypto.util.bytesToBase64(pubKeyHash); for (var i = 0; i < this.addressHashes.length; i++) { @@ -112,6 +163,12 @@ Bitcoin.Wallet = (function () { throw new Error("Missing key for signature"); }; + /** + * Retrieve the corresponding pubKey for a pubKeyHash. + * + * This function only works if the pubKey in question is part of this + * wallet. + */ this.getPubKeyFromHash = function (pubKeyHash) { pubKeyHash = Crypto.util.bytesToBase64(pubKeyHash); for (var i = 0; i < this.addressHashes.length; i++) { @@ -127,6 +184,12 @@ Bitcoin.Wallet = (function () { this.addKey(new Bitcoin.ECKey()); }; + /** + * Add a transaction to the wallet's processed transaction. + * + * This will add a transaction to the wallet, updating its balance and + * available unspent outputs. + */ Wallet.prototype.process = function (tx) { if (this.txIndex[tx.hash]) return;