247 lines
6.7 KiB
Python
247 lines
6.7 KiB
Python
|
#!/usr/bin/env python3
|
||
|
import csv
|
||
|
import os
|
||
|
import subprocess
|
||
|
|
||
|
#
|
||
|
# Constants
|
||
|
#
|
||
|
|
||
|
MAX_BLOCK_SIZE = 2000000
|
||
|
|
||
|
#
|
||
|
# Verification times
|
||
|
#
|
||
|
|
||
|
def collect_times():
|
||
|
# Find bench_bitcoin binary
|
||
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||
|
base_dir = os.path.dirname(os.path.dirname(script_dir))
|
||
|
bench_bitcoin = os.path.join(base_dir, 'src', 'bench', 'bench_bitcoin')
|
||
|
|
||
|
# Run bench_bitcoin binary
|
||
|
try:
|
||
|
result = subprocess.run([bench_bitcoin], stdout=subprocess.PIPE, universal_newlines=True)
|
||
|
result.check_returncode()
|
||
|
result = result.stdout
|
||
|
except AttributeError:
|
||
|
# Use the older API
|
||
|
result = subprocess.check_output([bench_bitcoin], universal_newlines=True)
|
||
|
|
||
|
# Collect benchmarks
|
||
|
benchmarks = {}
|
||
|
for row in result.strip().split('\n')[1:]: # Skip the headings
|
||
|
parts = row.split(',')
|
||
|
benchmarks[parts[0]] = int(parts[2])
|
||
|
|
||
|
return {
|
||
|
'proof': benchmarks['SaplingOutput'],
|
||
|
'ecdsa': benchmarks['ECDSA'],
|
||
|
'redjubjub': benchmarks['SaplingSpend'] - benchmarks['SaplingOutput'],
|
||
|
'ed25519': benchmarks['JoinSplitSig'],
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Size calculations
|
||
|
#
|
||
|
|
||
|
def compact_size_size(nSize):
|
||
|
if nSize < 253:
|
||
|
return 1
|
||
|
elif nSize <= 0xFFFF:
|
||
|
return 3
|
||
|
elif nSize <= 0xFFFFFFFF:
|
||
|
return 5
|
||
|
else:
|
||
|
return 9
|
||
|
|
||
|
def script_size(script):
|
||
|
return (
|
||
|
compact_size_size(len(script)) +
|
||
|
len(script)
|
||
|
)
|
||
|
|
||
|
def tx_in_size(scriptSig):
|
||
|
return (
|
||
|
32 + # prevout.hash
|
||
|
4 + # prevout.n
|
||
|
script_size(scriptSig) +
|
||
|
4 # nSequence
|
||
|
)
|
||
|
|
||
|
def tx_out_size(scriptPubKey):
|
||
|
return (
|
||
|
8 + # nValue
|
||
|
script_size(scriptPubKey)
|
||
|
)
|
||
|
|
||
|
def v4_tx_size(vin, vout, nShieldedSpend, nShieldedOutput, nJoinSplit):
|
||
|
return (
|
||
|
4 + # header
|
||
|
4 + # nVersionGroupId
|
||
|
compact_size_size(len(vin)) +
|
||
|
sum([tx_in_size(scriptSig) for scriptSig in vin]) +
|
||
|
compact_size_size(len(vout)) +
|
||
|
sum([tx_out_size(scriptPubKey) for scriptPubKey in vout]) +
|
||
|
4 + # lock_time
|
||
|
4 + # nExpiryHeight
|
||
|
4 + # valueBalance
|
||
|
compact_size_size(nShieldedSpend) +
|
||
|
(384 * nShieldedSpend) +
|
||
|
compact_size_size(nShieldedOutput) +
|
||
|
(948 * nShieldedOutput) +
|
||
|
compact_size_size(nJoinSplit) +
|
||
|
(1698 * nJoinSplit) +
|
||
|
((32 + 64) if nJoinSplit > 0 else 0) + # joinSplitPubKey + joinSplitSig
|
||
|
(64 if (nShieldedSpend + nShieldedOutput) > 0 else 0) # bindingSig
|
||
|
)
|
||
|
|
||
|
def block_size(vtx):
|
||
|
return (
|
||
|
4 + # nVersion
|
||
|
32 + # hashPrevBlock
|
||
|
32 + # hashMerkleRoot
|
||
|
32 + # hashFinalSaplingRoot
|
||
|
4 + # nTime
|
||
|
4 + # nBits
|
||
|
32 + # nNonce
|
||
|
compact_size_size(1344) + # solutionSize
|
||
|
1344 + # solution
|
||
|
compact_size_size(len(vtx)) +
|
||
|
sum([v4_tx_size(**tx) for tx in vtx])
|
||
|
)
|
||
|
|
||
|
#
|
||
|
# Runners
|
||
|
#
|
||
|
|
||
|
def worst_case_many_identical_txs(tx):
|
||
|
vtx = []
|
||
|
while True:
|
||
|
vtx.append(tx)
|
||
|
if block_size(vtx) > MAX_BLOCK_SIZE:
|
||
|
# Keep under the size limit
|
||
|
vtx.pop()
|
||
|
break
|
||
|
return vtx
|
||
|
|
||
|
def worst_case_one_tx_containing(item):
|
||
|
vtx = [{
|
||
|
'vin': [],
|
||
|
'vout': [],
|
||
|
'nShieldedSpend': 0,
|
||
|
'nShieldedOutput': 0,
|
||
|
'nJoinSplit': 0,
|
||
|
}]
|
||
|
while True:
|
||
|
vtx[0][item] += 1
|
||
|
if block_size(vtx) > MAX_BLOCK_SIZE:
|
||
|
# Keep under the size limit
|
||
|
vtx[0][item] -= 1
|
||
|
break
|
||
|
return vtx
|
||
|
|
||
|
def print_makeup(vtx, times):
|
||
|
# One proof per Sapling spend, Sapling output, and JoinSplit
|
||
|
proofs = sum([tx['nShieldedSpend'] + tx['nShieldedOutput'] + tx['nJoinSplit'] for tx in vtx])
|
||
|
|
||
|
# One ECDSA signature per transparent input
|
||
|
ecdsa_sigs = sum([len(tx['vin']) for tx in vtx])
|
||
|
|
||
|
# One RedJubjub signature per Sapling spend (spendAuthSig) and per transaction (bindingSig)
|
||
|
redjubjub_sigs = sum([tx['nShieldedSpend'] + (
|
||
|
1 if tx['nShieldedSpend'] + tx['nShieldedOutput'] > 0 else 0) for tx in vtx])
|
||
|
|
||
|
# One Ed25519 signature per transaction that contains JoinSplits
|
||
|
ed25519_sigs = sum([1 if tx['nJoinSplit'] > 0 else 0 for tx in vtx])
|
||
|
|
||
|
print('- Block size: ', block_size(vtx), 'bytes')
|
||
|
print('- Transactions: ', len(vtx))
|
||
|
print('- Proofs: ', proofs)
|
||
|
print('- ECDSA signatures: ', ecdsa_sigs)
|
||
|
print('- RedJubjub signatures:', redjubjub_sigs)
|
||
|
print('- Ed25519 signatures: ', ed25519_sigs)
|
||
|
print('- Verification time: %0.2f seconds' % (float(
|
||
|
(times['proof'] * proofs) +
|
||
|
(times['ecdsa'] * ecdsa_sigs) +
|
||
|
(times['redjubjub'] * redjubjub_sigs) +
|
||
|
(times['ed25519'] * ed25519_sigs)
|
||
|
) / 10**9))
|
||
|
|
||
|
#
|
||
|
# Worst-case scenarios
|
||
|
#
|
||
|
|
||
|
def worst_case_one_sapling_spend_per_tx(times):
|
||
|
vtx = worst_case_many_identical_txs({
|
||
|
'vin': [],
|
||
|
'vout': [],
|
||
|
'nShieldedSpend': 1,
|
||
|
'nShieldedOutput': 0,
|
||
|
'nJoinSplit': 0,
|
||
|
})
|
||
|
print('One Sapling spend per transaction:')
|
||
|
print_makeup(vtx, times)
|
||
|
print()
|
||
|
|
||
|
def worst_case_one_tx_containing_sapling_spends(times):
|
||
|
vtx = worst_case_one_tx_containing('nShieldedSpend')
|
||
|
print('One transaction containing Sapling spends:')
|
||
|
print_makeup(vtx, times)
|
||
|
print()
|
||
|
|
||
|
def worst_case_one_sapling_output_per_tx(times):
|
||
|
vtx = worst_case_many_identical_txs({
|
||
|
'vin': [],
|
||
|
'vout': [],
|
||
|
'nShieldedSpend': 0,
|
||
|
'nShieldedOutput': 1,
|
||
|
'nJoinSplit': 0,
|
||
|
})
|
||
|
print('One Sapling output per transaction:')
|
||
|
print_makeup(vtx, times)
|
||
|
print()
|
||
|
|
||
|
def worst_case_one_tx_containing_sapling_outputs(times):
|
||
|
vtx = worst_case_one_tx_containing('nShieldedOutput')
|
||
|
print('One transaction containing Sapling outputs:')
|
||
|
print_makeup(vtx, times)
|
||
|
print()
|
||
|
|
||
|
def worst_case_one_joinsplit_per_tx(times):
|
||
|
vtx = worst_case_many_identical_txs({
|
||
|
'vin': [],
|
||
|
'vout': [],
|
||
|
'nShieldedSpend': 0,
|
||
|
'nShieldedOutput': 0,
|
||
|
'nJoinSplit': 1,
|
||
|
})
|
||
|
print('One JoinSplit per transaction:')
|
||
|
print_makeup(vtx, times)
|
||
|
print()
|
||
|
|
||
|
def worst_case_one_tx_containing_joinsplits(times):
|
||
|
vtx = worst_case_one_tx_containing('nJoinSplit')
|
||
|
print('One transaction containing JoinSplits:')
|
||
|
print_makeup(vtx, times)
|
||
|
print()
|
||
|
|
||
|
|
||
|
def run():
|
||
|
print('Collecting benchmarks...')
|
||
|
times = collect_times()
|
||
|
# times = hard_coded_times()
|
||
|
print('Times (ns):', times)
|
||
|
print()
|
||
|
|
||
|
print('Running worst-case simulations...')
|
||
|
worst_case_one_sapling_spend_per_tx(times)
|
||
|
worst_case_one_tx_containing_sapling_spends(times)
|
||
|
worst_case_one_sapling_output_per_tx(times)
|
||
|
worst_case_one_tx_containing_sapling_outputs(times)
|
||
|
worst_case_one_joinsplit_per_tx(times)
|
||
|
worst_case_one_tx_containing_joinsplits(times)
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
run()
|