From 325d473349a12fdda00b272f940e6ee7c31c3fb2 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Tue, 28 Nov 2023 11:04:44 +0100 Subject: [PATCH 1/6] Update deprecated signrawtransaction RPC call signrawtransaction was replaced with signrawtransactionwithwallet in Bitcoin Core v0.17.0 Also added debug logging. --- otsclient/cmds.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/otsclient/cmds.py b/otsclient/cmds.py index d18c185..eea6002 100644 --- a/otsclient/cmds.py +++ b/otsclient/cmds.py @@ -57,14 +57,17 @@ def create_timestamp(timestamp, calendar_urls, args): if setup_bitcoin: proxy = setup_bitcoin() + logging.debug("Call fundrawtransaction for OP_RETURN %s", timestamp.msg.hex()) unfunded_tx = CTransaction([], [CTxOut(0, CScript([OP_RETURN, timestamp.msg]))]) r = proxy.fundrawtransaction(unfunded_tx) # FIXME: handle errors funded_tx = r['tx'] - r = proxy.signrawtransaction(funded_tx) + logging.debug("Call signrawtransactionwithwallet %s", funded_tx.serialize().hex()) + r = proxy.signrawtransactionwithwallet(funded_tx) assert r['complete'] signed_tx = r['tx'] + logging.debug("Call sendrawtransaction %s", signed_tx.serialize().hex()) txid = proxy.sendrawtransaction(signed_tx) logging.info('Sent timestamp tx') From 85df7bcaa6a4edc6020d8dfc401605018fcf86ed Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Tue, 28 Nov 2023 11:11:21 +0100 Subject: [PATCH 2/6] stamp: add --fee-rate argument --- otsclient/args.py | 3 +++ otsclient/cmds.py | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/otsclient/args.py b/otsclient/args.py index e84b07e..618c1e3 100644 --- a/otsclient/args.py +++ b/otsclient/args.py @@ -169,6 +169,9 @@ def parse_ots_args(raw_args): parser_stamp.add_argument('-b', '--btc-wallet', dest='use_btc_wallet', action='store_true', help='Create timestamp locally with the local Bitcoin wallet.') + parser_stamp.add_argument('-f', '--fee-rate', dest='fee_rate', default=False, + help='Specify fee rate in sat/vbyte. Default is to let Bitcoin Core decide.') + parser_stamp.add_argument('files', metavar='FILE', type=argparse.FileType('rb'), nargs='*', help='Filename') diff --git a/otsclient/cmds.py b/otsclient/cmds.py index eea6002..3617113 100644 --- a/otsclient/cmds.py +++ b/otsclient/cmds.py @@ -59,7 +59,12 @@ def create_timestamp(timestamp, calendar_urls, args): logging.debug("Call fundrawtransaction for OP_RETURN %s", timestamp.msg.hex()) unfunded_tx = CTransaction([], [CTxOut(0, CScript([OP_RETURN, timestamp.msg]))]) - r = proxy.fundrawtransaction(unfunded_tx) # FIXME: handle errors + + options = {} + if args.fee_rate: + options['fee_rate'] = args.fee_rate + + r = proxy.fundrawtransaction(unfunded_tx, options) # FIXME: handle errors funded_tx = r['tx'] logging.debug("Call signrawtransactionwithwallet %s", funded_tx.serialize().hex()) From c3d2187b00450dd5b5d123f77a460e842ebdd901 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Tue, 28 Nov 2023 11:30:38 +0100 Subject: [PATCH 3/6] stamp: allow interrupt and resume with nonce and txid Review hint: git diff --color-moved=dimmed-zebra --color-moved-ws=ignore-space-change --- otsclient/args.py | 11 +++++++++++ otsclient/cmds.py | 43 ++++++++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/otsclient/args.py b/otsclient/args.py index 618c1e3..8d31cc5 100644 --- a/otsclient/args.py +++ b/otsclient/args.py @@ -172,6 +172,12 @@ def parse_ots_args(raw_args): parser_stamp.add_argument('-f', '--fee-rate', dest='fee_rate', default=False, help='Specify fee rate in sat/vbyte. Default is to let Bitcoin Core decide.') + parser_stamp.add_argument('--nonce', dest='nonce', default=False, + help='Resume earlier stamp, must be used together with --txid') + + parser_stamp.add_argument('--txid', dest='txid', default=False, + help='Resume earlier stamp, must be used together with --nonce. Can be used with RBF.') + parser_stamp.add_argument('files', metavar='FILE', type=argparse.FileType('rb'), nargs='*', help='Filename') @@ -264,6 +270,11 @@ def parse_ots_args(raw_args): pass args = parser.parse_args(raw_args) + + if hasattr(args, "nonce") or hasattr(args, "txid"): + if bool(args.nonce) ^ bool(args.txid): + parser_stamp.error('--nonce and --txid must be given together') + args = handle_common_options(args, parser) return args diff --git a/otsclient/cmds.py b/otsclient/cmds.py index 3617113..cdc949f 100644 --- a/otsclient/cmds.py +++ b/otsclient/cmds.py @@ -45,7 +45,7 @@ def remote_calendar(calendar_uri): user_agent="OpenTimestamps-Client/%s" % otsclient.__version__) -def create_timestamp(timestamp, calendar_urls, args): +def create_timestamp(timestamp, nonce, calendar_urls, args): """Create a timestamp calendar_urls - List of calendar's to use @@ -57,26 +57,34 @@ def create_timestamp(timestamp, calendar_urls, args): if setup_bitcoin: proxy = setup_bitcoin() - logging.debug("Call fundrawtransaction for OP_RETURN %s", timestamp.msg.hex()) - unfunded_tx = CTransaction([], [CTxOut(0, CScript([OP_RETURN, timestamp.msg]))]) + txid = None + if args.txid: + logging.debug("Continue with existing transaction") + txid = bytes.fromhex(args.txid)[::-1] + else: + logging.debug("Call fundrawtransaction for OP_RETURN %s", timestamp.msg.hex()) + unfunded_tx = CTransaction([], [CTxOut(0, CScript([OP_RETURN, timestamp.msg]))]) - options = {} - if args.fee_rate: - options['fee_rate'] = args.fee_rate + options = {} + if args.fee_rate: + options['fee_rate'] = args.fee_rate - r = proxy.fundrawtransaction(unfunded_tx, options) # FIXME: handle errors - funded_tx = r['tx'] + r = proxy.fundrawtransaction(unfunded_tx, options) # FIXME: handle errors + funded_tx = r['tx'] - logging.debug("Call signrawtransactionwithwallet %s", funded_tx.serialize().hex()) - r = proxy.signrawtransactionwithwallet(funded_tx) - assert r['complete'] - signed_tx = r['tx'] + logging.debug("Call signrawtransactionwithwallet %s", funded_tx.serialize().hex()) + r = proxy.signrawtransactionwithwallet(funded_tx) + assert r['complete'] + signed_tx = r['tx'] - logging.debug("Call sendrawtransaction %s", signed_tx.serialize().hex()) - txid = proxy.sendrawtransaction(signed_tx) - logging.info('Sent timestamp tx') + logging.debug("Call sendrawtransaction %s", signed_tx.serialize().hex()) + txid = proxy.sendrawtransaction(signed_tx) + logging.info('Sent timestamp tx') blockhash = None + + logging.info('Waiting for confirmation. This can be interupted and resumed with:\nots stamp --nonce=%s --txid=%s', nonce.hex(), txid[::-1].hex()) + while blockhash is None: logging.info('Waiting for timestamp tx %s to confirm...' % b2lx(txid)) time.sleep(1) @@ -181,7 +189,8 @@ def stamp_command(args): # Remember that the files - and their timestamps - might get separated # later, so if we didn't use a nonce for every file, the timestamp # would leak information on the digests of adjacent files. - nonce_appended_stamp = file_timestamp.timestamp.ops.add(OpAppend(os.urandom(16))) + nonce = bytes.fromhex(args.nonce) if args.nonce else os.urandom(16) + nonce_appended_stamp = file_timestamp.timestamp.ops.add(OpAppend(nonce)) merkle_root = nonce_appended_stamp.ops.add(OpSHA256()) merkle_roots.append(merkle_root) @@ -196,7 +205,7 @@ def stamp_command(args): args.calendar_urls.append('https://a.pool.eternitywall.com') args.calendar_urls.append('https://ots.btc.catallaxy.com') - create_timestamp(merkle_tip, args.calendar_urls, args) + create_timestamp(merkle_tip, nonce, args.calendar_urls, args) if args.wait: upgrade_timestamp(merkle_tip, args) From 62a7a2ea8effe19f59520bbb907f247bdb0adadf Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Tue, 28 Nov 2023 11:42:27 +0100 Subject: [PATCH 4/6] Do not use calendars when solo stamping --- otsclient/cmds.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/otsclient/cmds.py b/otsclient/cmds.py index cdc949f..7e2532f 100644 --- a/otsclient/cmds.py +++ b/otsclient/cmds.py @@ -108,6 +108,10 @@ def create_timestamp(timestamp, nonce, calendar_urls, args): assert block_timestamp is not None timestamp.merge(block_timestamp) + # Do not use calendars when solo stamping: + if args.use_btc_wallet: + return True + m = args.m n = len(calendar_urls) if m > n or m <= 0: @@ -198,7 +202,7 @@ def stamp_command(args): merkle_tip = make_merkle_tree(merkle_roots) - if not args.calendar_urls: + if not args.use_btc_wallet or not args.calendar_urls: # Neither calendar nor wallet specified; add defaults args.calendar_urls.append('https://a.pool.opentimestamps.org') args.calendar_urls.append('https://b.pool.opentimestamps.org') From 18bcc9cc234f3a956a60ae2da1bb6de4a2449e41 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Tue, 28 Nov 2023 11:45:30 +0100 Subject: [PATCH 5/6] Add --btc-signet --- otsclient/args.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/otsclient/args.py b/otsclient/args.py index 8d31cc5..1110f38 100644 --- a/otsclient/args.py +++ b/otsclient/args.py @@ -54,6 +54,9 @@ def make_common_options_arg_parser(): btc_net_group.add_argument('--btc-testnet', dest='btc_net', action='store_const', const='testnet', default='mainnet', help='Use Bitcoin testnet rather than mainnet') + btc_net_group.add_argument('--btc-signet', dest='btc_net', action='store_const', + const='signet', default='mainnet', + help='Use Bitcoin signet rather than mainnet') btc_net_group.add_argument('--btc-regtest', dest='btc_net', action='store_const', const='regtest', help='Use Bitcoin regtest rather than mainnet') @@ -135,6 +138,8 @@ def setup_bitcoin(): """ if args.btc_net == 'testnet': bitcoin.SelectParams('testnet') + elif args.btc_net == 'signet': + bitcoin.SelectParams('signet') elif args.btc_net == 'regtest': bitcoin.SelectParams('regtest') elif args.btc_net == 'mainnet': From 640e9c8e90201931b3d29d2c1aae69fc513ada01 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Tue, 28 Nov 2023 11:59:40 +0100 Subject: [PATCH 6/6] README: document solo stamping --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 7df4c83..c687733 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,19 @@ commitment operations and attestations in it: append 647b90ea1b270a97 verify PendingAttestation('https://bob.btc.calendar.opentimestamps.org') +### Timestamping without a calendar service with your own wallet + +It's possible to create a timestamp using your own Bitcoin Core wallet. This +is generally not necessary, can harm your privacy and is somewhat risky because +the code is not well tested. + +Use the `--btc-wallet` argument after `stamp` command to use this feature, +optionally specifying a fee rate in sat/vbyte with `--fee-rate`. It is +possible to RBF the resulting transaction and then resume the process using +the `--txid` and `--nonce` arguments. + +See `ots stamp --help` for more info. + ### Timestamping and Verifying PGP Signed Git Commits See `doc/git-integration.md`