2019-07-24 14:50:07 +00:00
require ( 'dotenv' ) . config ( ) ;
const ethers = require ( 'ethers' ) ;
const fs = require ( 'fs' ) ;
2019-07-29 10:52:01 +00:00
const keypair = require ( '@chainsafe/bls-js/lib/keypair' ) ;
const privateKey = require ( '@chainsafe/bls-js/lib/privateKey' ) ;
2019-07-29 13:21:06 +00:00
const bls = require ( '@chainsafe/bls-js' ) ;
const sha256 = require ( 'js-sha256' ) ;
const ssz = require ( '@chainsafe/ssz' ) ;
const BN = require ( 'bn.js' ) ;
2019-07-24 14:50:07 +00:00
2019-07-29 10:52:01 +00:00
// Deposit contract data
2019-07-24 14:50:07 +00:00
const deposit _contract _bytecode = " 0x740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052341561009857600080fd5b6101406000601f818352015b600061014051602081106100b757600080fd5b600260c052602060c020015460208261016001015260208101905061014051602081106100e357600080fd5b600260c052602060c020015460208261016001015260208101905080610160526101609050602060c0825160208401600060025af161012157600080fd5b60c0519050606051600161014051018060405190131561014057600080fd5b809190121561014e57600080fd5b6020811061015b57600080fd5b600260c052602060c02001555b81516001018083528114156100a4575b505061124d56600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052600015610277575b6101605261014052600061018052610140516101a0526101c060006008818352015b61018051600860008112156100da578060000360020a82046100e1565b8060020a82025b905090506101805260ff6101a051166101e052610180516101e0516101805101101561010c57600080fd5b6101e0516101805101610180526101a0517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff86000811215610155578060000360020a820461015c565b8060020a82025b905090506101a0525b81516001018083528114156100bd575b50506018600860208206610200016020828401111561019357600080fd5b60208061022082610180600060046015f15050818152809050905090508051602001806102c0828460006004600a8704601201f16101d057600080fd5b50506102c05160206001820306601f82010390506103206102c0516008818352015b826103205111156102025761021e565b6000610320516102e001535b81516001018083528114156101f2575b50505060206102a05260406102c0510160206001820306601f8201039050610280525b6000610280511115156102535761026f565b602061028051036102a001516020610280510361028052610241565b610160515650005b63863a311b600051141561050957341561029057600080fd5b6000610140526101405161016052600154610180526101a060006020818352015b60016001610180511614156103325760006101a051602081106102d357600080fd5b600060c052602060c02001546020826102400101526020810190506101605160208261024001015260208101905080610240526102409050602060c0825160208401600060025af161032457600080fd5b60c0519050610160526103a0565b6000610160516020826101c00101526020810190506101a0516020811061035857600080fd5b600260c052602060c02001546020826101c0010152602081019050806101c0526101c09050602060c0825160208401600060025af161039657600080fd5b60c0519050610160525b61018060026103ae57600080fd5b60028151048152505b81516001018083528114156102b1575b505060006101605160208261046001015260208101905061014051610160516101805163806732896102e05260015461030052610300516006580161009b565b506103605260006103c0525b6103605160206001820306601f82010390506103c0511015156104355761044e565b6103c05161038001526103c0516020016103c052610413565b61018052610160526101405261036060088060208461046001018260208501600060046012f150508051820191505060006018602082066103e0016020828401111561049957600080fd5b60208061040082610140600060046015f150508181528090509050905060188060208461046001018260208501600060046014f150508051820191505080610460526104609050602060c0825160208401600060025af16104f957600080fd5b60c051905060005260206000f350005b63621fd130600051141561061c57341561052257600080fd5b63806732896101405260015461016052610160516006580161009b565b506101c0526000610220525b6101c05160206001820306601f82010390506102205110151561056d57610586565b610220516101e00152610220516020016102205261054b565b6101c0805160200180610280828460006004600a8704601201f16105a957600080fd5b50506102805160206001820306601f82010390506102e0610280516008818352015b826102e05111156105db576105f7565b60006102e0516102a001535b81516001018083528114156105cb575b5050506020610260526040610280510160206001820306601f8201039050610260f350005b63c47e300d60005114156110c857605060043560040161014037603060043560040135111561064a57600080fd5b60406024356004016101c037602060243560040135111561066a57600080fd5b608060
const deposit _contract _abi = '[{"name": "DepositEvent", "inputs": [{"type": "bytes", "name": "pubkey", "indexed": false}, {"type": "bytes", "name": "withdrawal_credentials", "indexed": false}, {"type": "bytes", "name": "amount", "indexed": false}, {"type": "bytes", "name": "signature", "indexed": false}, {"type": "bytes", "name": "index", "indexed": false}], "anonymous": false, "type": "event"}, {"outputs": [], "inputs": [], "constant": false, "payable": false, "type": "constructor"}, {"name": "get_hash_tree_root", "outputs": [{"type": "bytes32", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 91707}, {"name": "get_deposit_count", "outputs": [{"type": "bytes", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 10463}, {"name": "deposit", "outputs": [], "inputs": [{"type": "bytes", "name": "pubkey"}, {"type": "bytes", "name": "withdrawal_credentials"}, {"type": "bytes", "name": "signature"}], "constant": false, "payable": true, "type": "function", "gas": 1334230}]' ;
2019-07-29 10:52:01 +00:00
// Command-line arguments parser
2019-07-24 14:50:07 +00:00
var args = process . argv . slice ( 2 ) ;
var arglist = { } ;
args . forEach ( function ( val , index , array ) {
if ( val . indexOf ( "=" ) > - 1 ) {
let splitArg = val . split ( "=" ) ;
arglist [ splitArg [ 0 ] ] = splitArg [ 1 ] ;
} else {
arglist [ val ] = true ;
}
} ) ;
2019-07-29 10:52:01 +00:00
// @TODO make faucet balance configurable
2019-07-24 14:50:07 +00:00
const startupOptions = {
"mnemonic" : process . env . mnemonic ,
"default_balance_ether" : 1000000 ,
"total_accounts" : 1 ,
"db_path" : "./deploy/db" ,
2019-07-29 10:52:01 +00:00
"network_id" : 666 ,
"account_keys_path" : "./deploy/keys/validators.json" // Does not work yet: https://github.com/trufflesuite/ganache-cli/issues/663
2019-07-24 14:50:07 +00:00
} ;
2019-07-29 10:52:01 +00:00
// Default to 10 accounts if no options provided
if ( ! arglist . v && ! arglist . mykeys ) {
console . log ( "No number of auto-generated accounts specified, and no custom keys provided. Defaulting to 10 accounts." ) ;
arglist . v = 10 ;
}
// Generate `v` number of accounts with 32.1 ether each
2019-07-24 14:50:07 +00:00
var accounts = [ ] ;
if ( arglist . v && arglist . v > 0 ) {
accounts . push ( { "balance" : 0xD3C21BCECCEDA1000000 } ) ;
console . log ( "Creating " + arglist . v + " validator accounts and making deposits for them. Find their private keys in deploy/keys" ) ;
for ( var i = 0 ; i < arglist . v ; i ++ ) {
accounts . push ( { "balance" : 0x1BD7A1BED4A0A0000 } ) ; // 32.1 ether to each validator
}
startupOptions . accounts = accounts ;
}
2019-07-29 10:52:01 +00:00
// Feed all accounts in `.mykeys` with 32.1 ether
2019-07-24 20:46:37 +00:00
var myAccounts = [ ] ;
2019-07-29 10:52:01 +00:00
var mykeys ;
2019-07-24 20:46:37 +00:00
if ( arglist . mykeys ) {
try {
2019-07-29 10:52:01 +00:00
mykeys = require ( "./.mykeys.json" ) ;
2019-07-24 20:46:37 +00:00
} catch ( e ) {
console . error ( "Did you make sure .mykeys exists in this folder before running the command with the mykeys flag?" ) ;
return ;
}
for ( var key in mykeys ) {
if ( mykeys . hasOwnProperty ( key ) ) {
console . log ( "Queueing account " + key ) ;
myAccounts . push ( { "balance" : 0x1BD7A1BED4A0A0000 , "secretKey" : mykeys [ key ] } ) ;
}
}
if ( startupOptions . hasOwnProperty ( "accounts" ) ) {
console . log ( "Merging own keys with pre-generated" ) ;
startupOptions . accounts = startupOptions . accounts . concat ( myAccounts ) ;
} else {
console . log ( "Generating faucet account and appending queued keys." ) ;
myAccounts . unshift ( { "balance" : 0xD3C21BCECCEDA1000000 } ) ;
startupOptions . accounts = myAccounts ;
}
}
2019-07-29 10:52:01 +00:00
// Bootstrap Ganache
2019-07-24 20:46:37 +00:00
console . log ( "Bootstrapping with options:" ) ;
console . log ( startupOptions ) ;
2019-07-24 14:50:07 +00:00
const ganache = require ( "ganache-cli" ) ;
const provider = new ethers . providers . Web3Provider ( ganache . provider ( startupOptions ) ) ;
2019-07-29 10:52:01 +00:00
// Seed and export faucet account
2019-07-24 14:50:07 +00:00
var faucetAmount ;
provider . listAccounts ( ) . then ( function ( result ) {
provider . getBalance ( result [ 0 ] ) . then ( function ( balanceResult ) {
faucetAmount = balanceResult / 1e18 ;
let mnemonic = process . env . mnemonic ;
let mnemonicWallet = ethers . Wallet . fromMnemonic ( mnemonic ) ;
fs . writeFile ( "deploy/keys/faucetkey.txt" , mnemonicWallet . privateKey , function ( err ) {
if ( err ) {
return console . log ( err ) ;
}
console . log ( "Private key of faucet account " + result [ 0 ] + " now in /deploy/keys/faucetkey.txt. It is seeded with ~" + faucetAmount + " ether." ) ;
} ) ;
2019-07-24 20:46:37 +00:00
deployDepositContract ( mnemonicWallet . privateKey ) . then ( makeValidatorDeposits ) ;
2019-07-24 14:50:07 +00:00
} ) ;
} ) ;
2019-07-29 13:21:06 +00:00
let contractAddress ;
2019-07-29 10:52:01 +00:00
// Deploy deposit contract, precompiled, save its address into a file
2019-07-24 14:50:07 +00:00
async function deployDepositContract ( pk ) {
let factory = new ethers . ContractFactory ( deposit _contract _abi , deposit _contract _bytecode , new ethers . Wallet ( pk , provider ) ) ;
2019-07-29 13:21:06 +00:00
let contract = await factory . deploy ( ) ;
contractAddress = contract . address ;
2019-07-24 20:46:37 +00:00
console . log ( "Contract will be generated at " + contract . address + " when TX " + contract . deployTransaction . hash ) + " is mined." ;
2019-07-24 14:50:07 +00:00
fs . writeFile ( "deploy/keys/deposit_contract.txt" , contract . address , function ( err ) {
if ( err ) {
return console . log ( err ) ;
}
console . log ( "Contract address saved in deploy/keys." ) ;
} ) ;
2019-07-24 20:46:37 +00:00
await contract . deployed ( ) . then ( function ( ) { console . log ( "Contract deployed and ready." ) } ) ;
}
async function makeValidatorDeposits ( ) {
2019-07-29 10:52:01 +00:00
let accountMasterList = [ ] ;
if ( arglist . v ) {
console . log ( "Generating keys for " + arglist . v + " auto-generated accounts" ) ;
for ( var i = 0 ; i <= arglist . v ; i ++ ) {
let mnemonicWallet = new ethers . Wallet . fromMnemonic ( process . env . mnemonic , "m/44'/60'/0'/0/" + i ) ;
let pk = mnemonicWallet . privateKey ;
let bls _key _sign = new keypair . Keypair ( privateKey . PrivateKey . fromHexString ( pk ) ) . privateKey . toHexString ( ) ;
let bls _key _withdraw = new keypair . Keypair ( privateKey . PrivateKey . fromHexString ( invertHex ( pk ) ) ) . privateKey . toHexString ( ) ;
accountMasterList . push ( {
address : mnemonicWallet . address ,
pk : pk ,
bls _key _sign : bls _key _sign ,
bls _key _withdraw , bls _key _withdraw
} ) ;
}
}
if ( arglist . mykeys ) {
console . log ( "Generating keys for " + Object . keys ( mykeys ) . length + " pre-provided accounts" ) ;
for ( var key in mykeys ) {
if ( mykeys . hasOwnProperty ( key ) ) {
let pkWallet = new ethers . Wallet ( mykeys [ key ] ) ;
pk = pkWallet . privateKey ;
let bls _key _sign = new keypair . Keypair ( privateKey . PrivateKey . fromHexString ( pk ) ) . privateKey . toHexString ( ) ;
let bls _key _withdraw = new keypair . Keypair ( privateKey . PrivateKey . fromHexString ( invertHex ( pk ) ) ) . privateKey . toHexString ( ) ;
accountMasterList . push ( {
address : key ,
pk : pk ,
bls _key _sign : bls _key _sign ,
bls _key _withdraw , bls _key _withdraw
} ) ;
}
}
}
console . log ( "Saving keys to file" ) ;
fs . writeFile ( "deploy/keys/validators.json" , JSON . stringify ( accountMasterList ) , function ( err ) {
if ( err ) {
return console . log ( err ) ;
}
console . log ( "Validator list saved in deploy/keys/validators.json." ) ;
} ) ;
console . log ( "Starting validator deposits" ) ;
2019-07-29 13:21:06 +00:00
let contract = new ethers . Contract ( contractAddress , deposit _contract _abi , provider ) ;
2019-07-29 10:52:01 +00:00
accountMasterList . forEach ( function ( item ) {
2019-07-29 13:21:06 +00:00
// 48 byte public key for signing
let signkeys = new keypair . Keypair ( privateKey . PrivateKey . fromHexString ( item . bls _key _sign ) ) ;
let sign _pubkey = signkeys . publicKey . toBytesCompressed ( ) ;
let sign _prikey = signkeys . privateKey . toBytes ( ) ;
2019-07-24 20:46:37 +00:00
2019-07-29 13:21:06 +00:00
// 48 byte public key for withdrawing
let withdraw _pubkey = new keypair . Keypair ( privateKey . PrivateKey . fromHexString ( item . bls _key _withdraw ) ) . publicKey . toHexString ( ) ;
// Withdrawal credentials is the sha256 hash of the withdrawal pubkey (32 bytes), but the first byte of the hash is replaced with the prefix (currently 0 for version 0)
//let withdrawal_credentials = "00" + sha256.sha256(withdraw_pubkey).slice(2); // 32 byte output
let withdrawal _credentials = Buffer . from ( sha256 . arrayBuffer ( withdraw _pubkey ) )
// Signature is technically bls_sign(signing_privkey, signing_root(deposit_data)) but due to the circular dependency the signature here is actually ignored (!!) and can be nothing, null, or random data.
let signature _dd = Buffer . alloc ( 5 ) ;
// Put it together somehow
let depositData = {
pubkey : sign _pubkey ,
withdrawalCredentials : withdrawal _credentials ,
signature : signature _dd ,
amount : new BN ( "32000000000" )
}
console . log ( depositData ) ;
console . log ( sign _prikey ) ;
// Signature for Deposit call, should be identical to signature of deposit data 🤔 ¯\_(ツ)_/¯
let signature _d = bls . default . sign ( sign _prikey , ssz . signingRoot ( depositData , {
fields : [
[ "pubkey" , "bytes48" ] ,
[ "withdrawalCredentials" , "bytes32" ] ,
[ "amount" , "uint64" ] ,
[ "signature" , "bytes96" ] ,
] ,
} ) ) ;
// A signer is needed to sign a transaction from a given account
let signer = provider . getSigner ( item . address ) ;
contract . connect ( signer ) ;
let tx = contract . deposit ( sign _pubkey , withdrawalCredentials , signature _d ) ;
console . log ( "Validator " + item . address + " is depositing 32 ether to the deposit contract at " + contractAddress + " via TX " + tx . hash ) ;
tx . wait ( ) ;
} ) ;
2019-07-24 20:46:37 +00:00
2019-07-29 10:52:01 +00:00
const server = ganache . server ( startupOptions ) ;
server . listen ( 8545 , function ( err , blockchain ) {
// The server starts, you can connect to it with RPC now.
console . log ( "Server is running, feel free to connect!" ) ;
2019-07-24 20:46:37 +00:00
} ) ;
2019-07-29 10:52:01 +00:00
}
function invertHex ( hexString ) {
hexString = hexString . replace ( "0x" , "" ) ;
return "0x" + hexString . split ( "" ) . reverse ( ) . join ( "" ) ;
2019-07-24 14:50:07 +00:00
}