Skip to content

Instantly share code, notes, and snippets.

@metalefty
Last active September 28, 2020 09:03
Show Gist options
  • Save metalefty/f4f1d644cecc75f28f16f759f202e726 to your computer and use it in GitHub Desktop.
Save metalefty/f4f1d644cecc75f28f16f759f202e726 to your computer and use it in GitHub Desktop.
require 'open-uri'
require 'fileutils'
require 'optparse'
require 'securerandom'
@wrkdir='/tmp/tapyrus-bootstrap'
@tmpdir=""
def bootstrap
begin
Dir.mkdir(@wrkdir)
rescue Errno::EEXIST
ensure
@tmpdir=%x(mktemp --tmpdir=#{@wrkdir} --directory).strip
Dir.chdir(@tmpdir)
end
end
def signer_setup(*args)
%x(tapyrus-signer.setup #{args.join(' ')})
end
def core_genesis(*args)
%x(tapyrus-core.genesis #{args.join(' ')})
end
def bx(*args)
begin
Dir.mkdir(@wrkdir)
rescue Errno::EEXIST
end
unless File.exist?("#{@wrkdir}/bx")
bxurl = 'https://github.com/libbitcoin/libbitcoin-explorer/releases/download/v3.2.0/bx-linux-x64-qrcode'
open("#{@wrkdir}/bx", 'wb') do |output|
URI.open(bxurl) do |f|
output.write(f.read)
end
end
FileUtils.chmod(0500, "#{@wrkdir}/bx")
end
%x(#{@wrkdir}/bx #{args.join(' ')})
end
def key_pairs(n)
(1..n).map { |i| signer_setup('createkey').split(' ').reverse }.to_h
end
def vss_map(command, key_pairs, t)
vsss = key_pairs.values.map do |priv|
result = signer_setup("#{command} --private-key=#{priv} --threshold=#{t.to_s}" +
key_pairs.keys.map { |pub| " --public-key=#{pub}" }.join)
result.split(/\R/).map { |line| line.split(':') }.to_h
end
key_pairs.keys.map { |pub| [pub, vsss.map { |vss| vss[pub] }] }.to_h
end
def aggregates(key_pairs, vss_map)
aggregates_pub = ''
key_pairs.map do |pub, priv|
result = signer_setup("aggregate --private-key=#{priv}" + vss_map[pub].map { |vss| " --vss=#{vss}" }.join)
aggregates_pub = result.split(' ')[0].split(/\R/)[0]
[pub, result.split(' ')[1].split(/\R/)[0]]
end.to_h.merge({ pub: aggregates_pub })
end
def address()
address_key = bx("ec-new #{bx('seed')}")
public = bx("ec-to-public #{address_key}")
address = bx("ec-to-address #{public}").split(/\R/)[0]
{ address_key: address_key, address: address }
end
def genesis_block(aggregate_pub)
address = address()
genesis_block = core_genesis("-time=1563342688 -address=#{address[:address]} -signblockpubkey=#{aggregate_pub}").split(/\R/)[0]
address.merge({ genesis_block: genesis_block })
end
def signs(key_pairs, vss_map, aggregates, block, t)
key_pairs.map do |pub, priv|
signer_setup("sign --aggregated-public-key=#{aggregates[:pub]} --node-secret-share=#{aggregates[pub]}" +
" --private-key=#{priv} --block=#{block} --threshold=#{t}" +
vss_map[pub].map { |vss| " --block-vss=#{vss}" }.join).split(/\R/)[0]
end
end
def computesig(key_pairs, sigs, block_vsss, node_vsss, aggregates, block, t)
key_pairs.map do |pub, priv|
signer_setup("computesig --aggregated-public-key=#{aggregates[:pub]} --node-secret-share=#{aggregates[pub]}" +
" --private-key=#{priv} --block=#{block} --threshold=#{t}" +
block_vsss[pub].map { |vss| " --block-vss=#{vss}" }.join +
node_vsss[pub].map { |vss| " --node-vss=#{vss}" }.join +
sigs.map { |sig| " --sig=#{sig}" }.join)
end
end
def federations_toml(key_pairs, t, aggregates, node_vss_map)
key_pairs.map do |pub, priv|
"[[federation]]\n" +
"block-height = 0\n" +
"threshold = #{t}\n" +
"aggregated-public-key = '#{aggregates[:pub]}'\n" +
"node-vss = [\n" +
node_vss_map[pub].map { |vss| " '#{vss}'" }.join(",\n") +
"\n]"
end
end
def signer_toml(key_pairs, addrs, rpc_ips, opts)
key_pairs.map do |pub, priv|
<<"EOS"
[signer]
to-address = '#{addrs[pub][:address]}'
public-key = '#{pub}'
federations-file = '/root/snap/tapyrus-signer/common/federations.toml'
[rpc]
rpc-endpoint-host = '#{rpc_ips[pub]}'
rpc-endpoint-port = 2377
rpc-endpoint-user = '#{opts[:rpc_user]}'
rpc-endpoint-pass = '#{opts[:rpc_pass]}'
[redis]
redis-host = '#{opts[:redis]}'
redis-port = 6379
[general]
round-duration = #{opts[:round_duration]}
log-quiet = true
log-level = 'info'
log-file = '/root/snap/tapyrus-signer/common/log/tapyrus-signer.log'
skip-waiting-ibd = true
EOS
end
end
def tapyrus_conf(opts)
<<"EOS"
networkid=#{opts[:networkid]}
txindex=1
server=1
rest=1
rpcuser=#{opts[:rpc_user]}
rpcpassword=#{opts[:rpc_pass]}
rpcbind=0.0.0.0
rpcallowip=0.0.0.0/0
addseeder=#{opts[:seeder]}
EOS
end
def main(opts)
# def main(n, t, network_id, seeder, redis_id, rpc_ips)
key_pairs = key_pairs(opts[:signers])
node_vss_map = vss_map('createnodevss', key_pairs, opts[:min_signers])
aggregates = aggregates(key_pairs, node_vss_map)
block_vss_map = vss_map('createblockvss', key_pairs, opts[:min_signers])
address_and_genesis_block = genesis_block(aggregates[:pub])
sigs = signs(key_pairs, block_vss_map, aggregates, address_and_genesis_block[:genesis_block], opts[:min_signers])
block_with_signature = computesig(key_pairs, sigs, block_vss_map, node_vss_map, aggregates, address_and_genesis_block[:genesis_block], opts[:min_signers])
federations_toml = federations_toml(key_pairs, opts[:min_signers], aggregates, node_vss_map)
addrs = key_pairs.keys.map { |pub| [pub, address()] }.to_h
rpc_ips_m = key_pairs.keys.map.with_index { |pub, i| [pub, opts[:rpcendpoint][i % opts[:rpcendpoint].size]] }.to_h
signer_toml = signer_toml(key_pairs, addrs, rpc_ips_m, opts)
tapyrus_conf = tapyrus_conf(opts)
{ key_pairs: key_pairs, node_vss_map: node_vss_map, aggregates: aggregates,
block_vss_map: block_vss_map, sigs: sigs, block_with_signature: block_with_signature,
federations_toml: federations_toml, signer_toml: signer_toml, addrs: addrs, tapyrus_conf: tapyrus_conf }.merge(address_and_genesis_block)
end
def optparse
options = {}
opt = OptionParser.new
opt.on('--signers=VALUE', 'number of signers') {|v| options[:signers] = v.to_i}
opt.on('--min-signers=VALUE', 'minimum number of signers to create block') {|v| options[:min_signers] = v.to_i}
opt.on('--networkid=[VALUE]', 'tapyrus network id (random if empty)') {|v| options[:networkid] = v.to_i}
opt.on('--seeder=VALUE', 'seeder hostname') {|v| options[:seeder] = v}
opt.on('--redis=VALUE', 'hostname of Redis server') {|v| options[:redis] = v}
opt.on('--rpc-endpoint=VALUE', 'comma separated') {|v| options[:rpcendpoint] = v.split(',') }
opt.on('--rpc-user=VALUE', 'rpc username') {|v| options[:rpc_user] = v}
opt.on('--rpc-pass=VALUE', 'rpc password') {|v| options[:rpc_pass] = v}
opt.on('--round-duration=[VALUE]', 'round duration (in seconds, 600 if empty)') {|v| options[:round_duration] = v.to_i }
opt.parse(%w(-h)) if ARGV.empty?
opt.parse(ARGV)
# raise error if mandatory arguments are not given
raise ArgumentError if options[:signers].nil?
raise ArgumentError if options[:min_signers].nil?
raise ArgumentError if options[:seeder].nil?
raise ArgumentError if options[:redis].nil?
raise ArgumentError if options[:rpcendpoint].nil?
raise ArgumentError if options[:rpc_user].nil?
raise ArgumentError if options[:rpc_pass].nil?
options[:networkid] ||= SecureRandom.random_number(2**32-1) - 33550335
options[:round_duration] ||= 600 # 10 minutes
options
end
opts = optparse
bootstrap
result = main(opts)
opts[:signers].times.with_index {|i| Dir.mkdir("#{@tmpdir}/#{i}")}
result[:addrs].values.each.with_index { |addr, i| `echo "#{addr[:address_key]}" > #{i}/address_key`; `echo "#{addr[:address]}" > #{i}/address` }
result[:key_pairs].keys.each.with_index { |pub, i| `echo "#{pub}" > #{i}/pub_key` }
result[:key_pairs].values.each.with_index { |priv, i| `echo "#{priv}" > #{i}/priv_key` }
result[:federations_toml].each.with_index { |toml, i| `echo "#{toml}" > #{i}/federations.toml` }
result[:signer_toml].each.with_index { |toml, i| `echo "#{toml}" > #{i}/signer.toml` }
`echo "#{result[:block_with_signature].first}" > genesis.#{opts[:networkid]}`
`echo "#{result[:tapyrus_conf]}" > tapyrus.conf`
$stderr.puts "configurations has been dumped into #{@tmpdir}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment