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

Add OpSignToContract with tag 0x09 #14

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

apoelstra
Copy link
Member

No description provided.

@apoelstra
Copy link
Member Author

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

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

Or actually, Secp256k1SigCommitment?

@apoelstra
Copy link
Member Author

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
Copy link
Member Author

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

@apoelstra
Copy link
Member Author

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

@petertodd
Copy link
Member

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
Copy link
Member Author

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

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
Copy link
Member Author

Ok, added

@RCasatta
Copy link
Member

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
Copy link

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

@petertodd
Copy link
Member

@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
Copy link
Member

@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
Copy link
Member

@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
Copy link
Member

@petertodd Perfect, thanks

@petertodd
Copy link
Member

@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
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
Copy link
Member

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
Copy link
Member Author

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

@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')
Copy link
Contributor

Choose a reason for hiding this comment

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

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
Copy link
Member Author

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

Choose a reason for hiding this comment

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

@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?

Copy link
Member Author

Choose a reason for hiding this comment

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

@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.

Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

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

Copy link
Member

Choose a reason for hiding this comment

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

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

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
Copy link
Member Author

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

Choose a reason for hiding this comment

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

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

Copy link
Member Author

Choose a reason for hiding this comment

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

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

Copy link
Member

Choose a reason for hiding this comment

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

Thanks!

@petertodd
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.

@apoelstra
Copy link
Member Author

I should update this to be consistent with BlockstreamResearch/secp256k1-zkp#111 when that gets merged.

Or perhaps I should hold off until the Taproot equivalent.

Interesting thought to interpret invalid points as indicating a different kind of commitment. Now I would suggest using a BIP340 tagged hash with ots/invalidpoint as the tag, or something like that. I agree that, assuming you're right that all existing commitment opcodes are infallible, it would be nice if we didn't have to change that.

@petertodd
Copy link
Member

Holding off until Taproot is fine by me. As you can see, I'm in no rush. :)

Good point re: BIP340.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants