diff --git a/otsclient/args.py b/otsclient/args.py index e84b07e..89f568a 100644 --- a/otsclient/args.py +++ b/otsclient/args.py @@ -181,6 +181,31 @@ def parse_ots_args(raw_args): help="Consider the timestamp complete if at least M calendars reply prior to the timeout. " "Default: %(default)s") + # ----- stamp-digest ----- + parser_stamp_digest = subparsers.add_parser('stamp-digest', + help='Timestamp a digest') + + parser_stamp_digest.add_argument('-c', '--calendar', metavar='URL', dest='calendar_urls', action='append', type=str, + default=[], + help='Create timestamp with the aid of a remote calendar. May be specified multiple times.') + + parser_stamp_digest.add_argument('-b', '--btc-wallet', dest='use_btc_wallet', action='store_true', + help='Create timestamp locally with the local Bitcoin wallet.') + + parser_stamp_digest.add_argument('hex_digest', metavar='DIGEST', + help='32-byte hex-encoded SHA256 digest') + + parser_stamp_digest.add_argument('timestamp_fd', metavar='TIMESTAMP', type=argparse.FileType('wb'), + help='Timestamp filename') + + parser_stamp_digest.add_argument("--timeout", type=int, default=5, + help="Timeout before giving up on a calendar. " + "Default: %(default)d") + + parser_stamp_digest.add_argument("-m", type=int, default="2", + help="Consider the timestamp complete if at least M calendars reply prior to the timeout. " + "Default: %(default)s") + # ----- upgrade ----- parser_upgrade = subparsers.add_parser('upgrade', aliases=['u'], help='Upgrade remote calendar timestamps to be locally verifiable') @@ -236,6 +261,7 @@ def parse_ots_args(raw_args): parser_stamp.set_defaults(cmd_func=otsclient.cmds.stamp_command) + parser_stamp_digest.set_defaults(cmd_func=otsclient.cmds.stamp_digest_command) parser_upgrade.set_defaults(cmd_func=otsclient.cmds.upgrade_command) parser_verify.set_defaults(cmd_func=otsclient.cmds.verify_command) parser_info.set_defaults(cmd_func=otsclient.cmds.info_command) diff --git a/otsclient/cmds.py b/otsclient/cmds.py index d18c185..3ecab5c 100644 --- a/otsclient/cmds.py +++ b/otsclient/cmds.py @@ -208,6 +208,47 @@ def stamp_command(args): logging.error("Failed to create timestamp %r: %s" % (timestamp_file_path, exp)) sys.exit(1) +def stamp_digest_command(args): + try: + digest = binascii.unhexlify(args.hex_digest.encode('utf8')) + except binascii.Error as exp: + logging.error("Bad digest %r: %s" % (args.hex_digest, exp)) + sys.exit(1) + + if len(digest) != 32: + logging.error("Bad digest %r: got %s bytes; expected 32" % (args.hex_digest, len(digest))) + sys.exit(1) + + timestamp_file = DetachedTimestampFile(OpSHA256(), Timestamp(digest)) + + # Add nonce + # + # 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 = timestamp_file.timestamp.ops.add(OpAppend(os.urandom(16))) + merkle_tip = nonce_appended_stamp.ops.add(OpSHA256()) + + if 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) + + if args.wait: + upgrade_timestamp(merkle_tip, args) + logging.info("Timestamp complete; saving") + + try: + ctx = StreamSerializationContext(args.timestamp_fd) + timestamp_file.serialize(ctx) + except IOError as exp: + logging.error("Failed to create timestamp file: %s" % (exp)) + sys.exit(1) + def is_timestamp_complete(stamp, args): """Determine if timestamp is complete and can be verified""" for msg, attestation in stamp.all_attestations():