-
Notifications
You must be signed in to change notification settings - Fork 39
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
apoelstra
wants to merge
6
commits into
opentimestamps:master
Choose a base branch
from
apoelstra:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
e99cbbd
core: add support for multiplication of secp256k1 points by scalars
apoelstra 4877210
core: add OpSecp256k1Commitment
apoelstra 05db51e
Add examples/ directory with a sign-to-contract transaction
apoelstra 5ddb84e
Make OpSecp256k1Commitment a unary op
petertodd bbe30af
Improve ECC break comment
petertodd 6de546f
signtocontract: assert that points round-trip uniquely when hashing them
apoelstra File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
This is andytoshi on 2017-05-16 21:30 UTC |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
# Copyright (C) 2017 The OpenTimestamps developers | ||
# | ||
# This file is part of python-opentimestamps. | ||
# | ||
# It is subject to the license terms in the LICENSE file found in the top-level | ||
# directory of this distribution. | ||
# | ||
# No part of python-opentimestamps including this file, may be copied, | ||
# modified, propagated, or distributed except according to the terms contained | ||
# in the LICENSE file. | ||
|
||
import hashlib | ||
|
||
from opentimestamps.core.op import UnaryOp, MsgValueError | ||
|
||
@UnaryOp._register_op | ||
class OpSecp256k1Commitment(UnaryOp): | ||
"""Map (P || commit) -> [P + sha256(P||commit)G]_x for a given secp256k1 point P | ||
|
||
This is a unary op rather than a binary op to allow timestamps to also | ||
timestamp the point itself; in the event of an ECC break this might be | ||
relevant. Such a break would not affect the integrity of the commitment, | ||
but knowledge of the underlying key may be interesting in its own right. | ||
""" | ||
TAG = b'\x09' | ||
TAG_NAME = 'secp256k1commitment' | ||
|
||
def _do_op_call(self, msg): | ||
if len(msg) < 33: | ||
raise MsgValueError("Missing secp256k1 point") | ||
|
||
pt = Point.decode(msg[0:33]) | ||
assert(pt.encode() == msg[0:33]) | ||
|
||
hasher = hashlib.sha256() | ||
hasher.update(msg[0:33]) | ||
hasher.update(msg[33:]) | ||
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') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
||
|
||
## What follows is a lot of inefficient but explicit secp256k1 math | ||
class Point(object): | ||
inf = True | ||
x = 0 | ||
y = 0 | ||
|
||
def __init__(self, x=0, y=0): | ||
self.x = x | ||
self.y = y | ||
if x == 0 and y == 0: | ||
self.inf = True | ||
else: | ||
self.inf = False | ||
|
||
def __repr__(self): | ||
if self.inf: | ||
return "Point(infinity)" | ||
else: | ||
return "Point(%x, %x)" % (self.x, self.y) | ||
|
||
def __eq__(self, other): | ||
if isinstance(other, self.__class__): | ||
return (self.inf == True and other.inf == True) or\ | ||
(self.inf == False and other.inf == False and self.x == other.x and self.y == other.y) | ||
else: | ||
return False | ||
|
||
def __ne__(self, other): | ||
return not self.__eq__(other) | ||
|
||
@staticmethod | ||
def decode(data): | ||
if len(data) != 33 or (data[0] != 2 and data[0] != 3): | ||
raise MsgValueError("Incorrectly formatted public key") | ||
|
||
x = int.from_bytes(data[1:], 'big') | ||
if x >= SECP256K1_P: | ||
raise MsgValueError("out of range x coordinate for secp256k1 point") | ||
|
||
ysqr = (x ** 3 + 7) % SECP256K1_P | ||
y = psqrt(ysqr) | ||
if pow(y, 2, SECP256K1_P) != ysqr: | ||
raise MsgValueError("invalid x coordinate for secp256k1 point") | ||
|
||
if y % 2 == 1 and data[0] == 2: | ||
y = SECP256K1_P - y | ||
if y % 2 == 0 and data[0] == 3: | ||
y = SECP256K1_P - y | ||
|
||
return Point(x, y) | ||
|
||
def encode(self): | ||
ret = bytearray(self.x.to_bytes(33, 'big')) | ||
assert(ret[0] == 0) | ||
if self.y % 2 == 1: | ||
ret[0] = 3 | ||
else: | ||
ret[0] = 2 | ||
return ret | ||
|
||
def add(self, pt): | ||
if self.inf: | ||
return pt | ||
if pt.inf: | ||
return self | ||
|
||
if self.x == pt.x: | ||
if self.y == SECP256K1_P - pt.y: | ||
return Point() | ||
else: | ||
assert(self.y == pt.y) | ||
lam = (3 * self.x ** 2 * pinv(2 * self.y)) % SECP256K1_P | ||
else: | ||
lam = ((pt.y - self.y) * pinv(pt.x - self.x)) % SECP256K1_P | ||
|
||
x3 = (lam ** 2 - self.x - pt.x) % SECP256K1_P | ||
y3 = (self.y + lam * (x3 - self.x)) % SECP256K1_P | ||
|
||
return Point(x3, SECP256K1_P - y3) | ||
|
||
def scalar_mul(self, s): | ||
ret = Point() | ||
add = self | ||
s = s % SECP256K1_N | ||
while s > 0: | ||
if s % 2 == 1: | ||
ret = ret.add(add) | ||
add = add.add(add) # add | ||
s >>= 1 | ||
return ret | ||
|
||
SECP256K1_P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F | ||
SECP256K1_N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 | ||
SECP256K1_GEN = Point(0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, | ||
0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8) | ||
|
||
def pinv(x): | ||
return pow(x, SECP256K1_P - 2, SECP256K1_P) | ||
|
||
def psqrt(x): | ||
# using `>> 2` in place of `/ 4` keeps everything as an int rather than float | ||
return pow(x, (SECP256K1_P + 1) >> 2, SECP256K1_P) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# Copyright (C) 2017 The OpenTimestamps developers | ||
# | ||
# This file is part of python-opentimestamps. | ||
# | ||
# It is subject to the license terms in the LICENSE file found in the top-level | ||
# directory of this distribution. | ||
# | ||
# No part of python-opentimestamps including this file, may be copied, | ||
# modified, propagated, or distributed except according to the terms contained | ||
# in the LICENSE file. | ||
|
||
import hashlib | ||
import binascii | ||
import unittest | ||
|
||
from opentimestamps.core.secp256k1 import * | ||
|
||
class Test_Secp256k1(unittest.TestCase): | ||
def test_point_rt(self): | ||
"""Point encoding round trip""" | ||
gen = SECP256K1_GEN | ||
encode = gen.encode() | ||
self.assertEqual(encode, binascii.unhexlify("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")) | ||
gen2 = Point.decode(encode) | ||
self.assertEqual(gen, gen2) | ||
|
||
def test_pinv(self): | ||
"""Field inversion mod p""" | ||
self.assertEqual(pinv(1), 1) | ||
self.assertEqual(pinv(2), 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffe18) | ||
self.assertEqual(pinv(3), 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9fffffd75) | ||
self.assertEqual(2, pinv(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffe18)) | ||
self.assertEqual(3, pinv(0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9fffffd75)) | ||
|
||
def test_psqrt(self): | ||
"""Field square root mod p""" | ||
self.assertEqual(psqrt(1), 1) | ||
self.assertEqual(psqrt(2), 0x210c790573632359b1edb4302c117d8a132654692c3feeb7de3a86ac3f3b53f7) | ||
self.assertEqual(psqrt(4), 2) | ||
# may return the sqrt or its negative | ||
self.assertEqual(psqrt(9), SECP256K1_P - 3) | ||
self.assertEqual(psqrt(49), SECP256K1_P - 7) | ||
|
||
def test_point_add(self): | ||
"""Point adding and doubling""" | ||
|
||
inf = Point() | ||
# P random chosen by dice roll | ||
p1 = Point(0x394867ad93f5c9612e8d8b7600443334026e648e365337d799190e845d649e67, | ||
0x0b84af9a00c1a55a7ac03917e59b21c68d1ffdf18720c3ad279077049cfaaf63) | ||
# 2P | ||
p2 = Point(0x8e6575f6c759aea04a8ec65f61f71eba237a0af54292d41e3a4bac2efa922dea, | ||
0x2b3c07687787ff07ae312305f30481c451ae3b78d4f479a3b729615fedc040e4) | ||
# -2P | ||
np2 = Point(0x8e6575f6c759aea04a8ec65f61f71eba237a0af54292d41e3a4bac2efa922dea, | ||
0xd4c3f897887800f851cedcfa0cfb7e3bae51c4872b0b865c48d69e9f123fbb4b) | ||
# 3P | ||
p3 = Point(0x53dd5e495c7404790f9347470cc9c38ee239809c758f02ec04ba641ab3d0e043, | ||
0xd7a4f5e5bdf21000b1fe7216adbea92cb9917d8fea7b37628c1eddb409a5cd3f) | ||
|
||
self.assertEqual(inf.add(inf), inf) | ||
self.assertEqual(p1.add(inf), p1) | ||
self.assertEqual(inf.add(p1), p1) | ||
self.assertEqual(p1.add(p1), p2) | ||
self.assertEqual(p1.add(p2), p3) | ||
self.assertEqual(p2.add(p1), p3) | ||
self.assertEqual(p3.add(np2), p1) | ||
self.assertEqual(np2.add(p3), p1) | ||
self.assertEqual(p2.add(np2), inf) | ||
self.assertEqual(np2.add(p2), inf) | ||
|
||
def test_scalar_mul(self): | ||
inf = Point() | ||
# P random chosen by dice roll | ||
p1 = Point(0x394867ad93f5c9612e8d8b7600443334026e648e365337d799190e845d649e67, | ||
0x0b84af9a00c1a55a7ac03917e59b21c68d1ffdf18720c3ad279077049cfaaf63) | ||
# 2P | ||
p2 = Point(0x8e6575f6c759aea04a8ec65f61f71eba237a0af54292d41e3a4bac2efa922dea, | ||
0x2b3c07687787ff07ae312305f30481c451ae3b78d4f479a3b729615fedc040e4) | ||
# -2P | ||
np2 = Point(0x8e6575f6c759aea04a8ec65f61f71eba237a0af54292d41e3a4bac2efa922dea, | ||
0xd4c3f897887800f851cedcfa0cfb7e3bae51c4872b0b865c48d69e9f123fbb4b) | ||
# 3P | ||
p3 = Point(0x53dd5e495c7404790f9347470cc9c38ee239809c758f02ec04ba641ab3d0e043, | ||
0xd7a4f5e5bdf21000b1fe7216adbea92cb9917d8fea7b37628c1eddb409a5cd3f) | ||
|
||
# nP | ||
n = 0xa91ce154dcab9adabe08cc1ee84ec3cd0f426bbc08a54a1c41bd25f2587caedd | ||
pn = Point(0x9dc4b057a857ad2ef3535b4a207a7bfc9264e8fcacf718c895db7ead8d445b26, | ||
0x5af110ecb68636e5c352b69fc6348173932b83ca64587a91fd88af1446e33979) | ||
|
||
self.assertEqual(inf.scalar_mul(0), inf) | ||
self.assertEqual(inf.scalar_mul(1000), inf) | ||
self.assertEqual(inf.scalar_mul(-1), inf) | ||
|
||
self.assertEqual(p1.scalar_mul(0), inf) | ||
self.assertEqual(p1.scalar_mul(1), p1) | ||
self.assertEqual(p1.scalar_mul(2), p2) | ||
self.assertEqual(p1.scalar_mul(-2), np2) | ||
self.assertEqual(p2.scalar_mul(-1), np2) | ||
self.assertEqual(p1.scalar_mul(3), p3) | ||
|
||
def test_op_signtocontract(self): | ||
pt_encode = binascii.unhexlify("0308aec434612f56df3f02c4e678260424415882ebd3efc16d52e3f9c1e39afdb0") | ||
msg = hashlib.sha256("This is andytoshi on 2017-05-16 21:30 UTC".encode()).digest() | ||
result = binascii.unhexlify("d386ef692770fcecad43362cf541858662e4ebe31d3ad04d196f94168897947a") | ||
self.assertEqual(OpSecp256k1Commitment()(pt_encode + msg), result) | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!