#!/usr/bin/env python3 """Reproduce source-derived SPHINCS+ SHAKE-128s/128f size arithmetic. Run with Python 3 (standard library only): python3 analyze_sphincs_sizes.py # refetch pinned upstream sources python3 analyze_sphincs_sizes.py --offline # verify bundled source snapshots No cryptographic implementation, wallet binary, or benchmark is executed. """ import argparse from fractions import Fraction import hashlib import json from pathlib import Path import re import urllib.request REVISION = '7ec789ace6874d875f4bb84cb61b81155398167e' SOURCE_CHECK_DATE = '2026-09-21' SOURCE_RECORDS = [{'path': 'README.md', 'url': 'https://raw.githubusercontent.com/sphincs/sphincsplus/7ec789ace6874d875f4bb84cb61b81155398167e/README.md', 'sha256': '6bc7f7a5bb2d47059d7e6c0115e358ba832523a1f033c56e086988d66a46c754', 'bytes': 3108}, {'path': 'ref/params/params-sphincs-shake-128s.h', 'url': 'https://raw.githubusercontent.com/sphincs/sphincsplus/7ec789ace6874d875f4bb84cb61b81155398167e/ref/params/params-sphincs-shake-128s.h', 'sha256': 'da4f8c1ee5b475a8103d856d80dbec15276eb2f566ce44318dcd286faba05c1d', 'bytes': 2130}, {'path': 'ref/params/params-sphincs-shake-128f.h', 'url': 'https://raw.githubusercontent.com/sphincs/sphincsplus/7ec789ace6874d875f4bb84cb61b81155398167e/ref/params/params-sphincs-shake-128f.h', 'sha256': 'de37a1ad4de21af24c70262a97b74927a0decb8275eae1ad34dcf5ed52daa2e1', 'bytes': 2130}] ROOT = Path(__file__).resolve().parent def checked_sources(offline): texts = {} for record in SOURCE_RECORDS: local = ROOT / 'sphincs-size-sources' / record['path'] if offline: data = local.read_bytes() else: request = urllib.request.Request(record['url'], headers={ 'User-Agent': 'SYNX-editorial-source-check'}) with urllib.request.urlopen(request, timeout=30) as response: data = response.read() actual = hashlib.sha256(data).hexdigest() assert actual == record['sha256'], ('Source digest changed', record['path'], actual) assert len(data) == record['bytes'], ('Source byte length changed', record['path']) texts[record['path']] = data.decode('utf-8') return texts def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--offline', action='store_true') args = parser.parse_args() texts = checked_sources(args.offline) rows = {} for line in texts['README.md'].splitlines(): cells = [cell.strip() for cell in line.strip().strip('|').split('|')] if cells[0] in ('SPHINCS+-128s', 'SPHINCS+-128f'): assert len(cells) == 11, ('Unexpected table width', cells) assert cells[0] not in rows, ('Duplicate parameter row', cells[0]) rows[cells[0]] = [int(value.replace(',', '')) for value in cells[1:]] assert set(rows) == {'SPHINCS+-128s', 'SPHINCS+-128f'} expected = {'128s': (32, 64, 7856), '128f': (32, 64, 17088)} mapping = {'SPX_N': 0, 'SPX_FULL_HEIGHT': 1, 'SPX_D': 2, 'SPX_FORS_HEIGHT': 3, 'SPX_FORS_TREES': 4, 'SPX_WOTS_W': 5} sizes = {} checks = ['All three pinned source SHA-256 digests and byte lengths match.', 'README contains exactly one 128s row and one 128f row.'] for variant, expected_sizes in expected.items(): row = rows['SPHINCS+-' + variant] assert tuple(row[-3:]) == expected_sizes, ('Unexpected key/signature sizes', variant) header = texts['ref/params/params-sphincs-shake-' + variant + '.h'] for macro, column in mapping.items(): match = re.search(r'^#define\s+' + macro + r'\s+(\d+)\s*$', header, re.M) assert match is not None, ('Missing parameter constant', variant, macro) assert int(match.group(1)) == row[column], ('SHAKE/README mismatch', variant, macro) sizes['SPHINCS+-SHAKE-' + variant] = dict(zip( ('public_key_bytes', 'secret_key_bytes', 'signature_bytes'), expected_sizes)) checks.append(variant + ': README sizes match expected values and all six SHAKE-header parameters.') small = sizes['SPHINCS+-SHAKE-128s']['signature_bytes'] fast = sizes['SPHINCS+-SHAKE-128f']['signature_bytes'] delta = fast - small ratio = Fraction(fast, small) reduction = Fraction(delta, fast) assert delta == 9232 assert ratio == Fraction(1068, 491) assert reduction == Fraction(577, 1068) checks.extend(['Signature delta is exactly 9232 bytes.', '128f/128s signature-size ratio is exactly 1068/491.', 'Reduction relative to 128f is exactly 577/1068.']) conclusion = ( 'We compared the upstream SPHINCS+ parameter table with its SHAKE-specific ' 'headers. Both variants list 32-byte public keys and 64-byte secret keys. ' 'The 128s signature is 7,856 bytes versus 17,088 bytes for 128f: 9,232 fewer ' 'bytes, a 54.03% reduction. This source-derived calculation measures neither ' 'runtime nor compressed storage and does not validate SYNX binaries or ' 'explain the project\'s historical parameter choice.') assert 40 <= len(conclusion.split()) <= 80 result = { 'analysis': 'SPHINCS+ SHAKE-128s versus SHAKE-128f published byte sizes', 'source_check_date': SOURCE_CHECK_DATE, 'source_repository': 'https://github.com/sphincs/sphincsplus', 'source_revision': REVISION, 'source_revision_date': '2024-04-15T16:30:51Z', 'primary_source': 'https://github.com/sphincs/sphincsplus/blob/' + REVISION + '/README.md', 'sources': SOURCE_RECORDS, 'method': 'Parse the pinned README parameter rows; match six parameters against the SHAKE-specific headers; calculate exact rational differences. No source code is compiled or executed.', 'parameter_table': sizes, 'calculations': { 'signature_bytes_saved_128s_vs_128f': delta, 'signature_ratio_128f_over_128s_exact': str(ratio), 'signature_ratio_128f_over_128s_decimal': round(float(ratio), 10), 'signature_reduction_128s_relative_to_128f_exact': str(reduction), 'signature_reduction_percent': round(float(reduction * 100), 10), 'signature_reduction_percent_display': f'{float(reduction * 100):.2f}%', 'public_key_byte_difference': 0, 'secret_key_byte_difference': 0, 'formulas': { 'delta': '17088 - 7856 = 9232', 'ratio': '17088 / 7856 = 1068 / 491', 'reduction': '(17088 - 7856) / 17088 = 577 / 1068'}}, 'assertions_passed': checks, 'conclusion': conclusion, 'limitations': [ 'Raw published key/signature sizes only; no signing or verification runtime measurement.', 'No compressed-storage, transaction-envelope, block-size, network-throughput or bandwidth benchmark.', 'No SYNX release binary was executed or independently validated.', 'No inference about why SYNX historically chose a parameter set.', 'No claim that SPHINCS+ names establish FIPS-conformant SLH-DSA product validation.']} output = ROOT / 'sphincs-size-analysis.json' output.write_text(json.dumps(result, indent=2, ensure_ascii=False) + '\n') print(json.dumps({'output': str(output), 'revision': REVISION, 'delta_bytes': delta, 'reduction_percent': f'{float(reduction * 100):.2f}%', 'checks': len(checks)}, indent=2)) if __name__ == '__main__': main()