Ryan X. Charles
11 years ago
11 changed files with 448 additions and 3 deletions
@ -0,0 +1,66 @@ |
|||||
|
var Armory = require('../lib/Armory'); |
||||
|
var Address = require('../lib/Address'); |
||||
|
|
||||
|
// Initial public key can be retrieved from paper backup
|
||||
|
|
||||
|
var PublicX = '9df5 23e7 18b9 1f59 a790 2d46 999f 9357 ccf8 7208 24d4 3076 4516 b809 f7ab ce4e'; |
||||
|
var PublicY = '66ba 5d21 4682 0dae 401d 9506 8437 2516 79f9 0c56 4186 cc50 07df c6d0 6989 1ff4'; |
||||
|
var pubkey = '04' + PublicX.split(' ').join('') + PublicY.split(' ').join(''); |
||||
|
|
||||
|
// Chain code can be generated by entering paper backup
|
||||
|
// on brainwallet.org/#chains or by using Armory.fromSeed() below
|
||||
|
|
||||
|
var chaincode = '84ac14bc4b388b33da099a0b4ee3b507284d99e1476639e36e5ca5e6af86481e'; |
||||
|
|
||||
|
var armory = new Armory(chaincode, pubkey); |
||||
|
|
||||
|
console.log('Deriving public keys for'); |
||||
|
console.log('------------------------'); |
||||
|
console.log('Chain code: %s', chaincode); |
||||
|
console.log('Public key: %s', pubkey); |
||||
|
console.log(''); |
||||
|
|
||||
|
for (var i = 0; i < 5; i++) { |
||||
|
console.log(Address.fromPubKey(armory.pubkey).as('base58')); |
||||
|
armory = armory.next(); |
||||
|
} |
||||
|
|
||||
|
// Derive first public key and chain code from seed
|
||||
|
var seed = [ |
||||
|
'aagh hjfj sihk ietj giik wwai awtd uodh hnji', |
||||
|
'soss uaku egod utai itos fijj ihgi jhau jtoo' |
||||
|
]; |
||||
|
|
||||
|
console.log(''); |
||||
|
console.log(''); |
||||
|
console.log('Deriving public keys for'); |
||||
|
console.log('------------------------'); |
||||
|
console.log('Seed: %s', seed.join(' ')); |
||||
|
console.log(''); |
||||
|
|
||||
|
// skip first public key
|
||||
|
var a = Armory.fromSeed(seed.join('\n')).next(); |
||||
|
|
||||
|
for (var i = 0; i < 5; i++) { |
||||
|
console.log(Address.fromPubKey(a.pubkey).as('base58')); |
||||
|
a = a.next(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
var mpk = '045a09a3286873a72f164476bde9d1d8e5c2bc044e35aa47eb6e798e325a86417f7c35b61d9905053533e0b4f2a26eca0330aadf21c638969e45aaace50e4c0c8784ac14bc4b388b33da099a0b4ee3b507284d99e1476639e36e5ca5e6af86481e'; |
||||
|
|
||||
|
console.log(''); |
||||
|
console.log(''); |
||||
|
console.log('Deriving public keys for'); |
||||
|
console.log('------------------------'); |
||||
|
console.log('Master Public Key: %s', mpk); |
||||
|
console.log(''); |
||||
|
|
||||
|
// skip first public key
|
||||
|
var b = Armory.fromMasterPublicKey(mpk).next(); |
||||
|
|
||||
|
for (var i = 0; i < 5; i++) { |
||||
|
console.log(Address.fromPubKey(b.pubkey).as('base58')); |
||||
|
b = b.next(); |
||||
|
} |
||||
|
|
@ -0,0 +1,58 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<style> |
||||
|
textarea { |
||||
|
width: 400px; |
||||
|
height: 100px; |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
<body> |
||||
|
<script src="../../browser/bundle.js"></script> |
||||
|
Enter you paper wallet seed:<br> |
||||
|
<textarea id="seed">aagh hjfj sihk ietj giik wwai awtd uodh hnji |
||||
|
soss uaku egod utai itos fijj ihgi jhau jtoo</textarea> |
||||
|
<br> |
||||
|
<input type="submit" onclick="updateResult()" value="Generate"> |
||||
|
<div id="result"></div> |
||||
|
<pre id="console"></pre> |
||||
|
<script> |
||||
|
var bitcore = require('bitcore'), |
||||
|
Address = bitcore.Address, |
||||
|
Armory = bitcore.Armory; |
||||
|
|
||||
|
var logs = document.getElementById('console'); |
||||
|
function log (msg) { |
||||
|
logs.insertAdjacentHTML('beforeend', msg + '\n'); |
||||
|
} |
||||
|
function clear_log () { |
||||
|
logs.innerHTML = ''; |
||||
|
} |
||||
|
|
||||
|
function getSeed() { |
||||
|
return document.getElementById('seed').value; |
||||
|
} |
||||
|
|
||||
|
function updateResult () { |
||||
|
clear_log(); |
||||
|
var seed = getSeed(); |
||||
|
|
||||
|
var a = Armory.fromSeed(seed); |
||||
|
log('Armory MPK: '); |
||||
|
log(''); |
||||
|
log(''); |
||||
|
log('<textarea>' + a.pubkey.toString('hex') + '' + a.chaincode.toString('hex') + '</textarea>'); |
||||
|
log(''); |
||||
|
log(''); |
||||
|
log('Some wallet addresses:'); |
||||
|
for (var i = 0; i < 5; i++) { |
||||
|
log(Address.fromPubKey(a.pubkey).as('base58')); |
||||
|
a = a.next(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
updateResult(); |
||||
|
</script> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,115 @@ |
|||||
|
var Point = require('./Point'), |
||||
|
Key = require('./Key'), |
||||
|
sha256 = require('../util').sha256, |
||||
|
twoSha256 = require('../util').twoSha256; |
||||
|
|
||||
|
/** |
||||
|
* For now, this class can only supports derivation from public key |
||||
|
* It doesn't support private key derivation (TODO). |
||||
|
* |
||||
|
* @example examples/Armory.js |
||||
|
*/ |
||||
|
function Armory (chaincode, pubkey) { |
||||
|
this.chaincode = new Buffer(chaincode, 'hex'); |
||||
|
this.pubkey = new Buffer(pubkey, 'hex'); |
||||
|
} |
||||
|
|
||||
|
Armory.prototype.generatePubKey = function () { |
||||
|
var pubKey = this.pubkey; |
||||
|
var chainCode = this.chaincode; |
||||
|
var chainXor = twoSha256(pubKey); |
||||
|
|
||||
|
for (var i = 0; i < 32; i++) |
||||
|
chainXor[i] ^= chainCode[i]; |
||||
|
|
||||
|
var pt = Point.fromUncompressedPubKey(pubKey); |
||||
|
pt = Point.multiply(pt, chainXor); |
||||
|
|
||||
|
var new_pubkey = pt.toUncompressedPubKey(); |
||||
|
|
||||
|
return new_pubkey; |
||||
|
}; |
||||
|
|
||||
|
Armory.prototype.next = function () { |
||||
|
var next_pubkey = this.generatePubKey(); |
||||
|
return new Armory(this.chaincode, next_pubkey); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* PS: MPK here represents the pubkey concatenated |
||||
|
* with the chain code. It is an unofficial standard. |
||||
|
* |
||||
|
* Armory will soon release an officially supported |
||||
|
* format: |
||||
|
* |
||||
|
* https://github.com/etotheipi/BitcoinArmory/issues/204#issuecomment-42217801
|
||||
|
*/ |
||||
|
Armory.fromMasterPublicKey = function (mpk) { |
||||
|
var pubkey = mpk.substr(0, 130); |
||||
|
var chaincode = mpk.substr(130, mpk.length); |
||||
|
return new Armory(chaincode, pubkey); |
||||
|
}; |
||||
|
|
||||
|
function decode (str) { |
||||
|
var from = '0123456789abcdef'; |
||||
|
var to = 'asdfghjkwertuion'; |
||||
|
var res = ''; |
||||
|
for (var i = 0; i < str.length; i++) |
||||
|
res += from.charAt(to.indexOf(str.charAt(i))); |
||||
|
return res; |
||||
|
} |
||||
|
|
||||
|
Armory.decodeSeed = function (seed) { |
||||
|
var keys = seed.trim().split('\n'); |
||||
|
var lines = []; |
||||
|
|
||||
|
for (var i = 0; i < keys.length; i++) { |
||||
|
var k = keys[i].replace(' ',''); |
||||
|
var raw = new Buffer(decode(k), 'hex'); |
||||
|
var data = raw.slice(0, 16); |
||||
|
lines.push(data); |
||||
|
} |
||||
|
|
||||
|
var privKey = Buffer.concat([ lines[0], lines[1] ]); |
||||
|
var chainCode = (lines.length==4) ? |
||||
|
Buffer.concat([ lines[2], lines[3] ]) : Armory.deriveChaincode(privKey); |
||||
|
|
||||
|
return { |
||||
|
privKey: privKey, |
||||
|
chainCode: chainCode |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
// Derive chain code from root key
|
||||
|
Armory.fromSeed = function (seed) { |
||||
|
var res = Armory.decodeSeed(seed); |
||||
|
// generate first public key
|
||||
|
var key = new Key(); |
||||
|
key.private = res.privKey; |
||||
|
key.compressed = false; |
||||
|
key.regenerateSync(); |
||||
|
|
||||
|
return new Armory(res.chainCode, key.public); |
||||
|
}; |
||||
|
|
||||
|
Armory.deriveChaincode = function (root) { |
||||
|
var msg = 'Derive Chaincode from Root Key'; |
||||
|
var hash = twoSha256(root); |
||||
|
|
||||
|
var okey = []; |
||||
|
var ikey = []; |
||||
|
for (var i = 0; i < hash.length; i++) { |
||||
|
okey.push(0x5c ^ hash[i]); |
||||
|
ikey.push(0x36 ^ hash[i]); |
||||
|
} |
||||
|
|
||||
|
okey = new Buffer(okey); |
||||
|
ikey = new Buffer(ikey); |
||||
|
|
||||
|
var m = new Buffer(msg, 'utf8'); |
||||
|
var a = sha256(Buffer.concat([ ikey, m ])); |
||||
|
var b = sha256(Buffer.concat([ okey, a ])); |
||||
|
return b; |
||||
|
}; |
||||
|
|
||||
|
module.exports = Armory; |
@ -0,0 +1,92 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var chai = chai || require('chai'); |
||||
|
var bitcore = bitcore || require('../bitcore'); |
||||
|
|
||||
|
var should = chai.should(); |
||||
|
|
||||
|
var Armory = bitcore.Armory; |
||||
|
var Address = bitcore.Address; |
||||
|
|
||||
|
/** |
||||
|
* This is the Armory root code that was used to generated the hard coded values in |
||||
|
* those tests: |
||||
|
*/ |
||||
|
|
||||
|
var seed = [ |
||||
|
'aagh hjfj sihk ietj giik wwai awtd uodh hnji', |
||||
|
'soss uaku egod utai itos fijj ihgi jhau jtoo' |
||||
|
].join('\n'); |
||||
|
|
||||
|
/* |
||||
|
* It was retrieved by creating a wallet in Armory and creating a paper backup. |
||||
|
* |
||||
|
* This is the public key as presented on the generated Armory paper wallets: |
||||
|
*/ |
||||
|
|
||||
|
var PublicX = '9df5 23e7 18b9 1f59 a790 2d46 999f 9357 ccf8 7208 24d4 3076 4516 b809 f7ab ce4e'; |
||||
|
var PublicY = '66ba 5d21 4682 0dae 401d 9506 8437 2516 79f9 0c56 4186 cc50 07df c6d0 6989 1ff4'; |
||||
|
var pubkey = '04' + PublicX.split(' ').join('') + PublicY.split(' ').join(''); |
||||
|
|
||||
|
/* |
||||
|
* This chain code was derived from the seed above: |
||||
|
*/ |
||||
|
var chaincode = '84ac14bc4b388b33da099a0b4ee3b507284d99e1476639e36e5ca5e6af86481e'; |
||||
|
|
||||
|
/* |
||||
|
* This is some addresses generated from the wallet: |
||||
|
*/ |
||||
|
var address = [ |
||||
|
'1PUzLkds8eHGjHPaW7v7h23bzmHjrRMVqz', |
||||
|
'1CGrip2uQUwhP2f3ARfbcrmtdwvWzELRmj', |
||||
|
'1BfBauMP4PX1ZBYrqH4K4R8KWrFfskrs7E', |
||||
|
'15emDCBVgBJLDP5cKxuwZ4Q77sfqEcwZvC', |
||||
|
'16tDJhMYBv1szZgRZCohWrzEvzX2bG7vEQ' |
||||
|
]; |
||||
|
|
||||
|
var instance, fromseed, first; |
||||
|
|
||||
|
describe('Armory', function() { |
||||
|
it('should initialze the main object', function() { |
||||
|
should.exist(Armory); |
||||
|
}); |
||||
|
|
||||
|
it('should be able to create instance from chaincode, pubkey', function() { |
||||
|
instance = new Armory(chaincode, pubkey); |
||||
|
should.exist(instance); |
||||
|
}); |
||||
|
|
||||
|
it('should be able to create instance from seed', function() { |
||||
|
fromseed = Armory.fromSeed(seed); |
||||
|
should.exist(fromseed); |
||||
|
}); |
||||
|
|
||||
|
it('fromseed should generate the expected chain code', function() { |
||||
|
should.equal(fromseed.chaincode.toString('hex'), chaincode.toString('hex')); |
||||
|
should.equal(fromseed.chaincode.toString('hex'), instance.chaincode.toString('hex')); |
||||
|
}); |
||||
|
|
||||
|
it('fromseed should be able to generate the first public key', function() { |
||||
|
first = fromseed.next(); |
||||
|
should.exist(first); |
||||
|
}); |
||||
|
|
||||
|
it('instance created from chaincode,pubkey and the first instance generated by fromseed should match', function() { |
||||
|
should.equal(first.pubkey.toString('hex'), instance.pubkey.toString('hex')); |
||||
|
should.equal(first.chaincode.toString('hex'), instance.chaincode.toString('hex')); |
||||
|
}); |
||||
|
|
||||
|
it('armory should generate the expected addresses for the given chaincode,pubkey', function() { |
||||
|
var addr, a; |
||||
|
a = instance; |
||||
|
for (var i = 0; i < address.length; i++) { |
||||
|
should.equal(Address.fromPubKey(a.pubkey).as('base58'), address[i]); |
||||
|
a = a.next(); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
Loading…
Reference in new issue