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

Add OpSignToContract with tag `0x09` #14

Open
wants to merge 6 commits into
base: master
from

Conversation

Projects
None yet
5 participants
@apoelstra
Copy link
Member

apoelstra commented May 17, 2017

No description provided.

@apoelstra

This comment has been minimized.

Copy link
Member Author

apoelstra commented May 17, 2017

Manually constructed a .ots with txid ff1dca15029d1df57a601f180308bcb6b91f2e8e129668452eaf066cd0668fa6 and verified that it parsed correctly, but since it is still unconfirmed I can't chase it all the way down to a Merkle root yet.

@petertodd

This comment has been minimized.

Copy link
Member

petertodd commented May 17, 2017

Niiice!

I should test the re-implementability of this by attempting to implement it in Rust or something.

We might want to call it OpSignToContractSha256. Also, I wonder if the term "contract" makes sense here - most readers probably won't get the reference going forward, even if it's often been called that in the past in the more niche Bitcoin use-cases.

Maybe call it something like EccSigCommitment?

@petertodd

This comment has been minimized.

Copy link
Member

petertodd commented May 17, 2017

Or actually, Secp256k1SigCommitment?

@apoelstra

This comment has been minimized.

Copy link
Member Author

apoelstra commented May 17, 2017

Here is a .ots that uses this going to block 466872

https://download.wpsoftware.net/bitcoin/wizardry/andytoshi.s2c.ots

Sadly I can't get a file for which this will verify as the original data was the text This is andytoshi on 2017-05-16 21:30 UTC but ots verify adds a newline. (So does sha256sum, irritatingly.)

ACK name change to Secp256k1SigCommitment, will update the PR

Is the tag 0x09 ok?

@apoelstra

This comment has been minimized.

Copy link
Member Author

apoelstra commented May 17, 2017

Actually how about just Secp256k1Commitment? This doesn't necessarily have to appear in a signature.

@apoelstra apoelstra force-pushed the apoelstra:master branch from 462c064 to 4e21b4b May 17, 2017

@apoelstra

This comment has been minimized.

Copy link
Member Author

apoelstra commented May 17, 2017

Update with renames, also changed the copyright year from 2016 to 2017

@apoelstra apoelstra force-pushed the apoelstra:master branch from 4e21b4b to 5a21625 May 17, 2017

@petertodd

This comment has been minimized.

Copy link
Member

petertodd commented May 17, 2017

Cool, I like Secp256k1Commitment too!

What do you mean by "ots verify adds a newline"? I mean, ots verify shouldn't be modifying file contents at all. You know about the -n option to echo right?

Tag 0x09 is fine for now; I need to do up a tag allocation list... (or maybe make it a note making it clear that we don't have one?)

@apoelstra

This comment has been minimized.

Copy link
Member Author

apoelstra commented May 17, 2017

Oh! It was actually vim silently adding a 0x0a to my file even though I made sure there were no newlines. I removed it with a hex editor and everything works.

You can, in fact, verify the timestamp
https://download.wpsoftware.net/bitcoin/wizardry/andytoshi.s2c.ots
with the file
https://download.wpsoftware.net/bitcoin/wizardry/andytoshi.s2c

:)

@petertodd

This comment has been minimized.

Copy link
Member

petertodd commented May 17, 2017

Nice! You should add those two files to the repo; just create an examples/ directory like is in the OpenTimestamps client; we can rearrange or whatever else later.

@apoelstra

This comment has been minimized.

Copy link
Member Author

apoelstra commented May 17, 2017

Ok, added

@RCasatta

This comment has been minimized.

Copy link
Member

RCasatta commented May 18, 2017

Hi @apoelstra this is amazing :)
I tested the example and it works. However I am a little bit surprised about ff1dca15029d1df57a601f180308bcb6b91f2e8e129668452eaf066cd0668fa6 size (224 bytes) which is greater than an op_return based one baaac9946198ecad5d8a116aead902ac98e892065adbb446aaee9e1f946e1194 (210 bytes)
ff1d.. is saving 34 bytes of the op_return but it's using a longer ScriptSig (139 vs 77) resulting in a bigger transaction. Is there room left to optimize the ScriptSig?

@hoffmabc

This comment has been minimized.

Copy link

hoffmabc commented May 18, 2017

You can just set noeol in binary mode in vim to prevent the newline @apoelstra

@petertodd

This comment has been minimized.

Copy link
Member

petertodd commented May 18, 2017

@RCasatta The point of Secp256k1Commitment isn't primarily to make timestmamps smaller, but rather cheaper: since they can be done at zero marginal cost people can timestamp stuff while they're performing Bitcoin transactions that they would have otherwise made anyway.

Now, it's true that we could get both by spending bare CHECKSIG outputs like the OpenTimestamps server does, but that's not the main intent here; once this is implemented I'd still like the calendars to continue making occasional OP_RETURN-based timestamps to protect us in the unlikely event of a major ECC break.

@RCasatta

This comment has been minimized.

Copy link
Member

RCasatta commented May 18, 2017

@petertodd I perfectly understand the point that you can leverage a transaction someone would made otherwise, but as you said this must come at zero marginal cost to them otherwise it's not viable. Since the ScriptSig of ff1d.. is longer than the average transaction this looked to me that this was not the case. But Maybe I am just not understanding andrew's ScriptSig

@petertodd

This comment has been minimized.

Copy link
Member

petertodd commented May 18, 2017

@RCasatta You've got it backwards: Andrews transaction is a normal sized scriptSig; the OpenTimestamps server uses shorter-than-average bare CHECKSIG outputs that can be spent with shorter-than-average scriptSigs.

@RCasatta

This comment has been minimized.

Copy link
Member

RCasatta commented May 18, 2017

@petertodd Perfect, thanks

@petertodd

This comment has been minimized.

Copy link
Member

petertodd commented May 18, 2017

@RCasatta Ah, I just noticed that @apoelstra's transaction was using the 65-byte uncompresed pubkey 0460b56a673caf822f0fa1ad0eb7b9681b001a92a5fb1699c9d0040d980a5fbfc87e92753e1633560dae70622b3e4b207fce6a4207397606d43635b97d33091a03

Most transactions these days use compressed pubkeys, which are 32 bytes shorter.

@apoelstra

This comment has been minimized.

Copy link
Member Author

apoelstra commented May 18, 2017

Yes, sorry, I generated very many keys a very long time ago and have not gotten through them all.. hence the uncompressed keys. This is totally separate from sign-to-contract.

@RCasatta

This comment has been minimized.

Copy link
Member

RCasatta commented Mar 15, 2018

I would like this to regain attention and I am doing a recap from what I can understand.

Relatively weak points on this are:

  • Currently, ots receipt security assumption does not rely on discrete log security, while an ots with sign-to-contract will (not completely sure about this, since the proposed op still require an hashing operation).
  • A segwit tx with a sign-to-contract will produce a bigger proof since it will need 2 merkle traversal, one for the signature to the coinbase and the other from the coin base to the header

Mitigations:

  • The first point could anyway be avoided by always creating multiple attestations, with or without sign-to-contract.
  • The second point does not concern me very much (out-of-consensus space is cheap) but anyhow we could mitigate the extra space needed by developing a shrink of the receipt, removing redundant information, like if you have 2 non-sign-to-contract confirmed attestation, you can get rid of the more recent one.

Advantages:

  • Wallet plugins like this, partnership with wallet provider or other services which already are creating tx could allow a faster confirmations at no extra costs.

In conclusions, I think we should merge this :)

Concept ACK

@apoelstra

This comment has been minimized.

Copy link
Member Author

apoelstra commented Mar 15, 2018

Sign-to-contract does not introduce a discrete log assumption. It depends only on the security of the underlying hash (in this case, SHA256).

@petertodd

This comment has been minimized.

Copy link
Member

petertodd commented Mar 18, 2018

@apoelstra Do you have a ELI5 explanation as to why that's true? Be good to add that in a comment or something.

tweak = int.from_bytes(hasher.digest(), 'big')
tweak_pt = SECP256K1_GEN.scalar_mul(tweak)
final_pt = pt.add(tweak_pt)
return final_pt.x.to_bytes(32, 'big')

This comment has been minimized.

@LeoComandini

LeoComandini Mar 18, 2018

Contributor

With DER encoding the x-coord of the ephemeral key may be encoded in less than 32 bytes, it happens 1 time out of 512.
Something like (final_pt.x.bit_length() + 7) // 8 instead of 32 should fix that.

@apoelstra

This comment has been minimized.

Copy link
Member Author

apoelstra commented Mar 19, 2018

@LeoComandini in Bitcoin we always serialize points as 33 bytes rather than using DER. Everything is much nicer with fixed-length objects.

@petertodd Sure, there is a (almost) bijection between 32-byte numbers x and points xG. Suppose discrete log is broken so anyone can run this map both ways. Then for all intents and purposes, the map x,k -> kG + H(kG, x)G map is x,k -> k + H(k, x). In the random oracle model, it's easy to see that this map can be used in place of H, since if H is uniformly random and independent of its input then the k-offset cannot affect that.

For fixed k you can also directly prove that first/second preimage resistance or collision resistance hold for H, then the same property will hold for x -> k + H(k,x). This is sufficient for pay-to-contract but not for timestamping, unfortunately.

pt = Point.decode(msg[0:33])

hasher = hashlib.sha256()
hasher.update(pt.encode())

This comment has been minimized.

@petertodd

petertodd Mar 23, 2018

Member

@apoelstra Question: could msg[0:33] ever be different than the output from pt.encode()? And what prevents two encoded points from mapping to the same decoded then encoded point?

This comment has been minimized.

@apoelstra

apoelstra Mar 27, 2018

Author Member

@petertodd

  1. Sure. But anything that doesn't throw will be identical to the output of pt.encode() for some point.
  2. The encoding includes the full x coordinate and the sign of the y coordinate of a point. If two points have the same x-coord and y-sign then they are the same point.

This comment has been minimized.

@petertodd

petertodd Mar 29, 2018

Member

Right, so they should be identical, which means I think we can actually use msg[0:33] instead of pt.encode()

See, my concern is if they ever aren't, we have a source of mutability which may allow someone to create a false timestamp.

This comment has been minimized.

@apoelstra

apoelstra Mar 31, 2018

Author Member

Ah, I see. Yep, they will always be identical so if you're more comfortable using msg[0:33] I can do that instead.

This comment has been minimized.

@petertodd

petertodd Apr 1, 2018

Member

Sounds like a good idea.

Also, if we think this should be mathematically impossible, maybe adding an assertion that tests this isn't a bad idea. Worst case is a DoS attack in a situation where something is quite wrong in our understanding.

@petertodd

This comment has been minimized.

Copy link
Member

petertodd commented Apr 9, 2018

FYI, looks like @LeoComandini has actually done a thesis that is in part on the subject of EC commitments! https://github.com/LeoComandini/Thesis/blob/master/main.pdf

@apoelstra apoelstra force-pushed the apoelstra:master branch from e3c3f60 to 6de546f Apr 13, 2018

@apoelstra

This comment has been minimized.

Copy link
Member Author

apoelstra commented Apr 13, 2018

Oh, nice! Chapter 5 of Leo's thesis has a proof of exactly what you want, in the random oracle model.

@petertodd rebased and added a commit which assets that point-reencoding is unique, and uses the message directly instead of hashing the re-encoding.


@UnaryOp._register_op
class OpSecp256k1Commitment(UnaryOp):
"""Map (P || commit) -> [P + sha256(P||commit)G]_x for a given secp256k1 point P

This comment has been minimized.

@petertodd

petertodd Apr 23, 2018

Member

This description isn't actually correct: the opcode isn't taking a "commit", but rather an arbitrary message, limited only by the MAX_MSG_LENGTH limitation.

So I'd suggest we change that line to:

Map (P || m) -> [P + sha256(P || m)G]_x for a given secp256k1 point P

This comment has been minimized.

@apoelstra

apoelstra Apr 23, 2018

Author Member

Sure, I'll reword this. I had meant "commit" in the sense of "thing that is being committed to".

This comment has been minimized.

@petertodd

petertodd Apr 23, 2018

Member

Thanks!

@petertodd

This comment has been minimized.

Copy link
Member

petertodd commented Apr 23, 2018

While other commitment operations work on arbitrary input - modulo the MAX_MSG_LENGTH restriction - Secp256k1 introduces new failure modes for when the secp256k1 point is invalid in various ways. I'm not sure this is a good thing, as it complicates error handling, something quite apparent if you try to implement this in a language like Rust that requires functions to specify what errors they may return.

An potential alternative would be to change the semantics in the case of an error to instead, say, output sha256(<unique prefix> + msg) (as in, hash the entire input to the opcode, including the point, with a specific unique prefix). The unique prefix should be the same for all types of errors; I'd suggest just picking a 128bit random number.

If we did this:

  • the opcode would be a well-defined and secure commitment on all inputs.
  • all inputs would produce the same length of output.
  • modulo a crypto break, the mapping of input to output would be unique to this opcode as the unique prefix is "out-of-band" hashed data (AKA collision resistance).
  • writing test cases becomes easier, as all inputs being valid means you don't need separate infrastructure for failures (which notably the tests are missing right now!).

The only drawback I can think of is consensus across buggy implementations: if an implementation accidentally raises an error when it shouldn't, that just causes the timestamp to be unusable immediately. With my proposed semantics you might further build on that timestamp by, say, adding additional opcodes that would then be impossible to fix later.

However, as I don't think that risk is unique to my proposal, as you could also have a buggy implementation that thinks a point is valid when it actually isn't. IMO the only lesson there is "don't screw up". :)

While I can't think of any other examples off the top of my head of an opcode that would have a similar problem, I can imagine us using this general approach in the future as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment