Added comments.

This commit is contained in:
Stefan Thomas 2012-01-11 10:41:52 +01:00
parent de21042bb7
commit 09e8c6e184
8 changed files with 392 additions and 33 deletions

View file

@ -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);

View file

@ -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)

View file

@ -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;

View file

@ -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);

View file

@ -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();

View file

@ -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.
*

View file

@ -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});
}

View file

@ -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;