Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix and improve solo stamping #141

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
19 changes: 19 additions & 0 deletions otsclient/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -169,6 +174,15 @@ 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('--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')
Expand Down Expand Up @@ -261,6 +275,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')

Sjors marked this conversation as resolved.
Show resolved Hide resolved
args = handle_common_options(args, parser)

return args
45 changes: 33 additions & 12 deletions otsclient/cmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -57,18 +57,34 @@ def create_timestamp(timestamp, calendar_urls, args):
if setup_bitcoin:
proxy = setup_bitcoin()

unfunded_tx = CTransaction([], [CTxOut(0, CScript([OP_RETURN, timestamp.msg]))])
r = proxy.fundrawtransaction(unfunded_tx) # FIXME: handle errors
funded_tx = r['tx']
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

r = proxy.fundrawtransaction(unfunded_tx, options) # FIXME: handle errors
funded_tx = r['tx']

r = proxy.signrawtransaction(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']

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())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't actually work as intended. You need to run ots stamp -b --nonce=x --txid=x. If the -b is left out stamping happens normally, ignoring the nonce and txid arguments.

Maybe we want --nonce and --txid to force -b on?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another issue we have right now is that if you use the txid and nonce options after a block has confirmed, ots fails to find the new block.

Maybe we could query the wallet for the transaction? Or maybe we also need a block height?

If we have all three options, we probably want to do it via a single --resume option that can find the tx even if the txid changes.


while blockhash is None:
logging.info('Waiting for timestamp tx %s to confirm...' % b2lx(txid))
time.sleep(1)
Expand All @@ -92,6 +108,10 @@ def create_timestamp(timestamp, 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:
Expand Down Expand Up @@ -173,22 +193,23 @@ 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))
Sjors marked this conversation as resolved.
Show resolved Hide resolved
merkle_root = nonce_appended_stamp.ops.add(OpSHA256())

merkle_roots.append(merkle_root)
file_timestamps.append(file_timestamp)

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')
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)
Expand Down