Commit 774493e8 authored by Alex Puig's avatar Alex Puig
Browse files

Merge branch 'bernat' into 'master'

Examples for this library

See merge request caelum-labs/poe-wallet!2
parents 72af013e e138cc04
# Caelum HDWallet
requires:
## Installing
```bash
git clone https://gitlab.com/caelum-labs/poe-wallet/
cd poe-wallet
npm install
```
## Testing
```bash
npm test
```
## Requires
- [BIP39](https://github.com/bitcoinjs/bip39)
- [ethereumjs-wallet](https://github.com/ethereumjs/ethereumjs-wallet)
- [ethereumjs-wallet/hdkey](https://github.com/ethereumjs/ethereumjs-wallet/hdkey)
- [ethereumjs-util](https://github.com/ethereumjs/ethereumjs-util)
- [ethereumjs-tx](https://github.com/ethereumjs/ethereumjs-tx)
- [JS SHA3 - Shake256](https://github.com/emn178/js-sha3)
- [HDKey](https://github.com/cryptocoinjs/hdkey)
- [Base64](https://github.com/dankogai/js-base64)
## WalletUtils class
- createMnemonic();
- verifySignature( base64Signed, address );
- addHexPrefix( str );
- pubToAddress( publicKeyBuffer );
- namespaceToNumber (namespace, hashLength);
- hashPersonalMessage( stringOrBuffer );
- ecsign (hash, privateKey);
methods:
- **wallet generation**
- createMnemonic();
- createPrivateKey( format: ['string' || 'buffer']);
- **initialization**
- constructor ([ ( address || publicKey ) || ( privateKey || mnemonic ) ])
- fromMnemonic( masterMnemonicSeed );
- fromPrivateKey( privateKey );
- fromPublicKey( publicKey );
- fromAddress( address );
- **keystore**
- addNamespace( namespace );
- hasNamespace( namespace );
- generateAddresses( namespace, addressCount );
- discardAddresses( namespace, [addressesToDiscard || addressCount] );
- getAddresses( namespace, count );
- hasAddress( namescpace, address );
- getNameSpaces();
- **signing and encrypting**
- signMessage ( messageStr, addressToUse );
- signTransaction ( objectTx, addressToUse );
- verifySignature (signed, address );
- [TODO] encrypt (string, addressToUse );
- [TODO] decrypt (string, addressToUse );
- **informational**
- balanceOf( [address] );
- getTransactionCount( [address] );
- transfer( amount, fromWallet );
## Wallet class
- constructor( mnemonicSeed );
- privateKey( format );
- publicKey( format );
- address( format );
- fromMnemonic( mnemonicSeed );
- fromHDKey( hdkey );
- generateAddresses( namespace, addressCount );
- getAddresses( namespace, addressCount );
- sign ( messageStr );
This diff is collapsed.
/*
* 01 - Create wallet
* Node.js example on how to create a manster wallet with
* it's seed words and retrieve it's propierties
*/
// Let's import our main file
const poeWallet = require('../app.js');
// We assign each lib to a single var
const walletUtils = poeWallet.util;
const Wallet = poeWallet.wallet;
// First we will create the mnemonic.
// Be sure to write down this words, all the wallets we work with from here on
// will be savely derived from these words. CAUTION!
let mnemonic = walletUtils.createMnemonic();
console.log('Mnemonic:', mnemonic);
// "bike spoon convince green sister call bind estate dream topple drastic green"
// We will now initiate a wallet using the mnemonic we just created
let masterWallet = new Wallet( mnemonic );
// We can check for it's address, public key and private key
// Never share your private key!!
console.log('private key:', masterWallet.privateKey('string') );
console.log('public key:', masterWallet.publicKey('string') );
console.log('address', masterWallet.address('string') );
/*
* 02 - Derive wallets
* Let's create a wallet under a namespace
*/
// Let's import our main file
const poeWallet = require('../app.js');
// We assign each lib to a single var
const walletUtils = poeWallet.util;
const Wallet = poeWallet.wallet;
// We generate a new unique mnemonic and masterWallet for this example.
let mnemonic = walletUtils.createMnemonic();
let masterWallet = new Wallet( mnemonic );
// Now we will use namespaces to generate purpose-oriented wallets
// Let's say we want 5 wallets to interact with ERC20 tokens
let namespaceERC20 = 'ERC20';
let walletsERC20 = masterWallet.generateAddresses( namespaceERC20, 5);
walletsERC20.forEach( (wallet, i) => {
console.log(
'Address:', wallet.address('string'),
'Count:', i,
'Namespace:', namespaceERC20
)
})
//Address: 0xbb27c0484977c6246f67ae6108aff8b4cb26d268 Count: 0 Namespace: ERC20
//Address: 0x22205839a6255f898629b1091d87b828229b2447 Count: 1 Namespace: ERC20
//Address: 0xf154781819ff7d5df6fd3f0a7d62705ce97f135e Count: 2 Namespace: ERC20
//Address: 0x590914b918cbd8e0d57ddfb78b4b51cbda79549c Count: 3 Namespace: ERC20
//Address: 0x7302fd5b7b4fc09b02cd80a5cb3f95e43e7754cc Count: 4 Namespace: ERC20
// Now we can operate with each generated wallet as we do with the master one,
// we can even generate sub-namespaces if needed
let gamesWallet = masterWallet.generateAddresses('games', 1)[0];
let cryptoKittiesWallet = gamesWallet.generateAddresses('cryptoKitties', 1)[0];
/*
* 03 - Sign and verify
* Let's sign some messages to prove that we own the signer wallet
*/
// Let's import our main file
const poeWallet = require('../app.js');
// We assign each lib to a single var
const walletUtils = poeWallet.util;
const Wallet = poeWallet.wallet;
// We generate a new unique mnemonic and masterWallet for this example.
let mnemonic = walletUtils.createMnemonic();
let masterWallet = new Wallet( mnemonic );
// Let's suppose we want a chat-like app
// We could assign each contact a subwallet to authenticate
// against each other
let alice = new Wallet( walletUtils.createMnemonic() );
let aliceToBob = alice.generateAddresses('messaging/bob', 1)[0];
let bob = new Wallet( walletUtils.createMnemonic() );
let bobToAlice = bob.generateAddresses('messaging/alice', 1)[0];
// When Alice and Bob have to communicate they will do using these wallets
let aliceMessage = 'hello bob!';
let aliceSigned = aliceToBob.sign(aliceMessage);
console.log('aliceToBob\'s signed message:', aliceSigned);
// Alice's signed message: 0x711df71c488af9f93730aa88961f0e6a506314cc538541713c699bb30d62702c0f21274c56d2a23ddbf13331118063a6d82e99a732932bef154124bdd5dcf59001
let didAliceSign = walletUtils.verifySignature(aliceSigned, aliceToBob.address('string'), aliceMessage);
console.log('Did aliceToBob sign the message?', didAliceSign)
// Did alice sign the message? true
module.exports = {
util : require('./lib/util.js'),
wallet : require('./lib/wallet.js')
}
const bip39 = require('bip39')
const EthereumjsUtil = require('ethereumjs-util')
const shake256 = require('js-sha3').shake256;
const util = {
/**
* Creates a random mnemonic
* @returns {string} - Mnemonic
*/
createMnemonic: () => {
return bip39.generateMnemonic();
},
/**
* Verifies that a given signature (b64 encoded) is signed by an address
* @param {string} b64Signed - Signature to verify
* @param {string} address - Address to verify against
* @returns {boolean} - If signature was performed by address
*/
verifySignature : (b64Signed, address ) => {
let json = Base64.decode(b64Signed);
let params = JSON.parse(json);
let isValid = EthereumjsUtil.isValidSignature(
params.v,
Buffer.from( params.r, 'hex'),
Buffer.from( params.s, 'hex')
)
// retrieve address that signed the message
let messageSigner = EthereumjsUtil.addHexPrefix(
EthereumjsUtil.pubToAddress(
EthereumjsUtil.ecrecover(
Buffer.from( params.hash, 'hex'),
params.v,
Buffer.from( params.r, 'hex'),
Buffer.from( params.s, 'hex')
)
).toString('hex')
)
return (parseInt(address, 16) === parseInt(messageSigner, 16))
},
/**
* Adds the hex prefix ('0x') to hex strings, if not already there
* @param {string} str - Hex string to add prefix to
* @returns {string} - Prefixed hex string
*/
addHexPrefix: ( str ) => {
return EthereumjsUtil.addHexPrefix( str );
},
/**
* Turns a public key into an address
* @param {Buffer} publicKeyBuffer - Public key buffer
* @returns {string} - Derived address
*/
pubToAddress: (publicKeyBuffer) => {
return EthereumjsUtil.pubToAddress(publicKeyBuffer, 'true');
},
/**
* Turns a namespace string into a number
* @param {string} namespace - Namespace for generation.
* @param {number} hashLength - Length for the shake256 hash generated.
* @returns {number} - Keccak, as number, of given namespace
*/
namespaceToNumber: ( namespace, hashLength ) =>{
return parseInt(
shake256( namespace, hashLength),
16
)
},
/**
* Hashes a message
* @param {string} stringOrBuffer - String or buffer representing the message
* @returns {Buffer} -Hashed message
*/
hashPersonalMessage: (stringOrBuffer) => {
return EthereumjsUtil.hashPersonalMessage(stringOrBuffer);
},
/**
* Signs a hash with given privateKey
* @param {Buffer} hash - Hash to sign
* @param {Buffer} privateKey - Private key to sign with
* @returns {Object} - Signature object
*/
ecsign: (hash, privateKey) => {
return EthereumjsUtil.ecsign(hash, privateKey)
},
}
module.exports = util;
// third party libs
const bip39 = require('bip39')
// NOT USED : const ethereumjsWallet = require('ethereumjs-wallet')
const ethereumjsWalletHDkey = require('ethereumjs-wallet/hdkey')
//const EthereumjsUtil = require('ethereumjs-util')
const EthereumjsTx = require('ethereumjs-tx')
const walletUtils = require('./util.js')
const HDKey = require('hdkey');
//let w = HDKey.fromMasterSeed( bip39.mnemonicToSeed(bip39.generateMnemonic()) )
//console.log(w)
class Wallet {
mnemonicSeed;
instance;
constructor(mnemonicSeed){
if (mnemonicSeed){
this.mnemonicSeed = mnemonicSeed;
this.instance = HDKey.fromMasterSeed(mnemonicSeed)
}
}
/**
* Retrieve private key once wallet is initialized.
* @param {string} format - Formatting : can be buffer or string.
* @returns {string} - private key for the given wallet instance.
*/
privateKey (format = 'buffer') {
if (!this.instance){
throw "Wallet not initialized, cannot retrieve private key";
}
let privateKey = this.instance._privateKey;
return (format === 'string') ? walletUtils.addHexPrefix( privateKey.toString('hex') ) : privateKey;
}
}
/*function Wallet(mnemonicSeed){
this.mnemonicSeed = false;
this.instance = false;
if (mnemonicSeed){
this.mnemonicSeed = mnemonicSeed;
this.instance = HDKey.fromMasterSeed(mnemonicSeed)
}
this.privateKey = false;
this.publicKey = false;
this.address = false;
}
*/
/**
* Retrieve private key once wallet is initialized.
* @param {string} format - Formatting : can be buffer or string.
* @returns {string} - private key for the given wallet instance.
*/
Wallet.prototype.privateKey = (format = 'buffer') => {
if (!this.instance){
throw "Wallet not initialized, cannot retrieve private key";
}
let privateKey = this.instance._privateKey;
return (format === 'string') ? walletUtils.addHexPrefix( privateKey.toString('hex') ) : privateKey;
}
/**
* Retrieve public key once wallet is initialized.
* @param {string} format - Formatting : can be buffer or string.
* @returns {string} - Public key for the given wallet instance.
*/
Wallet.prototype.publicKey = (format = 'buffer') => {
if (!this.instance){
throw "Wallet not initialized, cannot retrieve public key";
}
let publicKey = this.instance._publicKey;
return (format === 'string') ? walletUtils.addHexPrefix( publicKey.toString('hex') ) : publicKey;
}
/**
* Retrieve address once wallet is initialized.
* @param {string} format - Formatting : can be buffer or string.
* @returns {string} - Address for the given wallet instance.
*/
Wallet.prototype.address = (format = 'buffer') => {
if (!this.instance){
throw "Wallet not initialized, cannot retrieve address";
}
let publicKeyBuffer = this.instance._publicKey;
let address = walletUtils.pubToAddress(publicKeyBuffer);
return (format === 'string') ? walletUtils.addHexPrefix( address.toString('hex') ) : address;
}
/**
* Initializes privKey, pubKey and address from mnemonic.
* @param {string} masterMnemonicSeed - Mnemonic.
* @returns {string} - Address
*/
Wallet.prototype.fromMnemonic = ( mnemonicSeed ) => {
if (!bip39.validateMnemonic(mnemonicSeed)) {
throw new Error('Invalid mnemonic provided to Wallet.fromMnemonic :' + mnemonicSeed)
}
if (!Buffer.isBuffer(mnemonicSeed)){
mnemonicSeed = Buffer.from(mnemonicSeed, 'hex')
}
// privateKey.string = bip39.mnemonicToSeedHex( mnemonicSeed );
// privateKey.buffer = bip39.mnemonicToSeed( mnemonicSeed );
delete this.mnemonicSeed;
delete this.instance;
this.mnemonicSeed = mnemonicSeed
this.instance = HDKey.fromMasterSeed(mnemonicSeed)
/*
this.privateKey.buffer = this.masterHDWallet._hdkey._privateKey
this.privateKey.string = this.privateKey.buffer.toString('hex')
derivePublicKey()
hashAddress()
return wallet.address*/
}
Wallet.prototype.fromPublicKey = ( publicKey ) => {
}
Wallet.prototype.fromAddress = ( address ) => {
}
Wallet.prototype.addNamespace = ( namespace ) => {
}
Wallet.prototype.hasNamespace = ( namespace ) => {
}
Wallet.prototype.generateAddresses = ( namespace, addressCount ) => {
}
/*
Wallet.prototype.discardAddresses = ( namespace, [addressesToDiscard || addressCount] ) => {
}
*/
Wallet.prototype.getAddresses = ( namespace, count ) => {
}
Wallet.prototype.hasAddress = ( namescpace, address ) => {
}
Wallet.prototype.signMessage = ( messageStr, addressToUse ) => {
}
Wallet.prototype.signTransaction = ( objectTx, addressToUse ) => {
}
module.exports = Wallet;
// third party libs
const bip39 = require('bip39')
const HDKey = require('hdkey');
const Base64 = require('js-base64').Base64;
//own libs
const walletUtils = require('./util.js')
class Wallet {
/**
* Retrieve private key once wallet is initialized.
* @param {string} mnemonicSeed - Mnemonic Seed.
*/
constructor(mnemonicSeed){
this.shake256Length = 32;
if (mnemonicSeed){
this.fromMnemonic(mnemonicSeed)
}
}
/**
* Retrieve private key once wallet is initialized.
* @param {string} format - Formatting : can be buffer or string.
* @returns {string} - private key for the given wallet instance.
*/
privateKey (format = 'buffer') {
if (!this.instance){
throw new Error( "Wallet not initialized, cannot retrieve private key" )
}
let privateKey = this.instance._privateKey;
return (format === 'string') ? walletUtils.addHexPrefix( privateKey.toString('hex') ) : privateKey;
}
/**
* Retrieve public key once wallet is initialized.
* @param {string} format - Formatting : can be buffer or string.
* @returns {string} - Public key for the given wallet instance.
*/
publicKey (format = 'buffer') {
if (!this.instance){
throw new Error("Wallet not initialized, cannot retrieve public key");
}
let publicKey = this.instance._publicKey;
return (format === 'string') ? walletUtils.addHexPrefix( publicKey.toString('hex') ) : publicKey;
}
/**
* Retrieve address once wallet is initialized.
* @param {string} format - Formatting : can be buffer or string.
* @returns {string} - Address for the given wallet instance.
*/
address (format = 'buffer') {
if (!this.instance){
throw new Error( "Wallet not initialized, cannot retrieve address" );
}
let publicKeyBuffer = this.instance._publicKey;
let address = walletUtils.pubToAddress(publicKeyBuffer);
return (format === 'string') ? walletUtils.addHexPrefix( address.toString('hex') ) : address;
}
/**
* Initializes privKey, pubKey and address from mnemonic.
* @param {string} mnemonicSeed - Mnemonic.
*/
fromMnemonic ( mnemonicSeed ) {
if (!bip39.validateMnemonic(mnemonicSeed)) {
throw new Error('Invalid mnemonic provided to Wallet.fromMnemonic :' + mnemonicSeed)
}
delete this.mnemonicSeed;
delete this.instance;
this.mnemonicSeed = mnemonicSeed
this.instance = HDKey.fromMasterSeed(mnemonicSeed)
}
/**
* Initializes the current wallet with the given hdkey
* @param {string} masterMnemonicSeed - Mnemonic.
*/
fromHDKey (hdkey){
delete this.mnemonicSeed;
delete this.instance;
this.instance = hdkey;
}
/**
* Generates `addressCount` accounts on provided `namespace`
* @param {string} namespace - Namespace for generation.
* @param {number} addressCount - How many addresses are to be generated.
* @returns {Array} - Wallets
*/
generateAddresses ( namespace, addressCount ){
let numericNamespace = walletUtils.namespaceToNumber(namespace, this.shake256Length);
let wallets = [];
for (var i = 0; i < addressCount; i++){
let idx = numericNamespace + '' + i;
let child = this.instance.deriveChild(idx);
let wallet = new Wallet();
wallet.fromHDKey(child);
wallets.push(wallet);
}
return wallets;
}
/**
* Generates `addressCount` accounts on provided `namespace`
* @param {string} namespace - Namespace for generation.
* @param {number} addressCount - How many addresses are to be generated.
* @returns {Array} - Addresses
*/
getAddresses ( namespace, addressCount ) {
let wallets = this.generateAddresses(namespace, addressCount);
let addresses = [];
wallets.forEach( wallet => {
addresses.push(
walletUtils.addHexPrefix(
wallet.address('string')
)
)
})
return addresses;
}
/**
* Signs a given message with the current wallet
* @param {string} messageStr - Message to sign
* @returns {string} - Base64 encoded json object with hash and signature
*/
sign ( messageStr ){
let hash = walletUtils.hashPersonalMessage(Buffer.from( messageStr ));