Skip to content

Commit 08ca8ee

Browse files
committed
Example of secp256k1 EdDSA signature
1 parent 5b707b3 commit 08ca8ee

File tree

3 files changed

+103
-42
lines changed

3 files changed

+103
-42
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ test:
99
$(LUA) examples/pi.lua
1010
$(LUA) examples/e.lua
1111
$(LUA) examples/rsa.lua
12+
$(LUA) examples/secp256k1.lua
1213

1314
docs:
1415
ldoc -d docs -f markdown bint.lua

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Some interesting examples there:
8989
* [pi.lua](https://github.com/edubart/lua-bint/blob/master/examples/pi.lua) - calculate the first 100 digits of Pi
9090
* [e.lua](https://github.com/edubart/lua-bint/blob/master/examples/e.lua) - calculate the first 100 digits of Euler's number
9191
* [rsa.lua](https://github.com/edubart/lua-bint/blob/master/examples/rsa.lua) - simple RSA example for encrypting/decrypting messages
92-
* [secp256k1.lua](https://github.com/edubart/lua-bint/blob/master/examples/secp256k1.lua) - simple Secp256k1 elliptic curve example for encrypting/decrypting messages
92+
* [secp256k1.lua](https://github.com/edubart/lua-bint/blob/master/examples/secp256k1.lua) - simple Secp256k1 elliptic curve example for encrypting/decrypting/signing messages
9393

9494
## Tests
9595

examples/secp256k1.lua

Lines changed: 101 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-- Simple Elliptic Curve Cryptography of secp256k1 for encrypting/decrypting messages
1+
-- Simple Elliptic Curve Cryptography of secp256k1 for encrypting/decrypting/signing messages
22
-- See https://en.bitcoin.it/wiki/Secp256k1
33

44
local bint = require 'bint'(768)
@@ -17,6 +17,9 @@ end
1717
-- Curve prime number
1818
local P = bint('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f')
1919

20+
-- Curve order
21+
local N = bint('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141')
22+
2023
-- Curve parameters
2124
local A = bint(0)
2225
local B = bint(7)
@@ -28,6 +31,12 @@ CurvePoint.__index = CurvePoint
2831
-- Curve point at infinity
2932
local O = CurvePoint{x=bint.zero(), y=math.huge}
3033

34+
-- Curve generator point
35+
local G = CurvePoint{
36+
x = bint('0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),
37+
y = bint('0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8'),
38+
}
39+
3140
-- Checks if points in the curve are equal.
3241
function CurvePoint.__eq(a, b)
3342
return a.x == b.x and bint.eq(b.y, b.y)
@@ -82,14 +91,6 @@ function CurvePoint:valid()
8291
return rem:iszero()
8392
end
8493

85-
-- Curve generator point
86-
local G = CurvePoint{
87-
x = bint('0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),
88-
y = bint('0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8'),
89-
}
90-
91-
-- Curve order
92-
local N = bint('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141')
9394

9495
do -- Tests some curve operations
9596
assert(G:valid())
@@ -106,45 +107,104 @@ do -- Tests some curve operations
106107
assert(G*3 == G+G+G)
107108
end
108109

109-
-- Very simple XOR crypt (very insecure, but works for demo purposes, use a better cipher like AES!)
110-
local function xorcrypt(msg, symmetric_private_key)
111-
local xor_key = symmetric_private_key.x ~ symmetric_private_key.y
112-
return msg ~ xor_key
113-
end
110+
do -- Test encrypt and decrypt
111+
-- Very simple XOR crypt (very insecure, but works for demo purposes, use a better cipher like AES!)
112+
local function xorcrypt(msg, symmetric_private_key)
113+
local xor_key = symmetric_private_key.x ~ symmetric_private_key.y
114+
return msg ~ xor_key
115+
end
114116

115-
-- Encrypt message using a private symmetric key and the curve public key.
116-
local function encrypt(msg, symmetric_private_key, public_key)
117-
return xorcrypt(msg, public_key * symmetric_private_key)
118-
end
117+
-- Encrypt message using a private symmetric key and the curve public key.
118+
local function encrypt(msg, symmetric_private_key, public_key)
119+
return xorcrypt(msg, public_key * symmetric_private_key)
120+
end
121+
122+
-- Decrypt message using a public symmetric key and the curve private key.
123+
local function decrypt(msg, symmetric_public_key, private_key)
124+
return xorcrypt(msg, symmetric_public_key * private_key)
125+
end
126+
127+
print 'Message encryption test:'
128+
129+
-- Choose a private key
130+
local private_key = bint.frombe('This is my private key, hide it!')
131+
print('private_key = ' .. tostring(private_key))
132+
133+
-- Compute public key
134+
local public_key = G * private_key
135+
print('public_key = ' .. tostring(public_key))
136+
assert(public_key:valid())
137+
138+
-- Generate a random scalar (ideally should be a random number)
139+
local symmetric_private_key = bint.frombe('Symmetric key random, hide it!')
140+
local symmetric_public_key = G * symmetric_private_key
141+
assert(symmetric_public_key:valid())
119142

120-
-- Decrypt message using a public symmetric key and the curve secret key.
121-
local function decrypt(msg, symmetric_public_key, secret_key)
122-
return xorcrypt(msg, symmetric_public_key * secret_key)
143+
local message = bint.frombe('Hello world!')
144+
local encrypted_message = encrypt(message, symmetric_private_key, public_key)
145+
print('Message: ' .. message:tobe(true))
146+
print('encrypted_message = 0x' .. encrypted_message:tobase(16))
147+
148+
local decrypted_message = decrypt(encrypted_message, symmetric_public_key, private_key)
149+
print('Decoded: ' .. decrypted_message:tobe(true))
150+
assert(message == decrypted_message)
123151
end
124152

125-
-- Choose a secret key
126-
local secret_key = bint.frombe('This is my secret key, hide it!')
127-
print('secret_key = ' .. tostring(secret_key))
153+
do -- Test message signature using EdDSA (see https://en.wikipedia.org/wiki/EdDSA)
154+
print('Message signature test:')
155+
156+
-- Simple hash for demo purposes (not very secure, use a better hash like SHA-256!)
157+
local function hash(msg, sign_public_key, public_key)
158+
local function h(v) return (v << 13) ~ (v >> 17) ~ (v << 5) end
159+
local s = bint.zero()
160+
local k = bint('0x9ddfea08eb382d69')
161+
s = (h(msg) ~ s) * k; s = s ~ (s >> 47)
162+
s = (h(sign_public_key.x) ~ s) * k; s = s ~ (s >> 47)
163+
s = (h(sign_public_key.y) ~ s) * k; s = s ~ (s >> 47)
164+
s = (h(public_key.x) ~ s) * k; s = s ~ (s >> 47)
165+
s = (h(public_key.y) ~ s) * k; s = s ~ (s >> 47)
166+
return s % bint.ipow(2, 256)
167+
end
128168

129-
-- Compute public key
130-
local public_key = G * secret_key
131-
assert(public_key:valid())
132-
print('public_key = ' .. tostring(public_key))
169+
-- Signs a message using private keys
170+
local function sign(message, sign_private_key, private_key)
171+
local sign_public_key = G * sign_private_key
172+
local public_key = G * private_key
173+
local message_hash = hash(message, sign_public_key, public_key)
174+
local sign_binding_factor = (sign_private_key + private_key * message_hash) % N
175+
return sign_public_key, sign_binding_factor
176+
end
177+
178+
-- Verifies a message using public signature and public keys.
179+
local function verify(message, sign_binding_factor, sign_public_key, public_key)
180+
local message_hash = hash(message, sign_public_key, public_key)
181+
return sign_public_key + public_key * message_hash == G * sign_binding_factor
182+
end
133183

134-
-- Test encrypt and decrypt
135-
print 'Message encryption test:'
184+
-- Choose a private key
185+
local private_key = bint.frombe('This is my private key, hide it!')
186+
print('private_key = ' .. tostring(private_key))
136187

137-
-- Generate a random symmetric key (ideally should be a random number)
138-
local symmetric_private_key = bint.frombe('Symmetric key random, hide it!')
139-
local symmetric_public_key = G * symmetric_private_key
140-
assert(symmetric_public_key:valid())
188+
-- Compute public key
189+
local public_key = G * private_key
190+
print('public_key = ' .. tostring(public_key))
191+
assert(public_key:valid())
141192

142-
local message = bint.frombe('Hello world!')
143-
local encrypted_message = encrypt(message, symmetric_private_key, public_key)
144-
print('Message: ' .. message:tobe(true))
145-
print('encrypted_message = 0x' .. encrypted_message:tobase(16))
193+
-- Generate a random scalar (ideally should be a random number)
194+
local sign_private_key = bint.frombe('Signature random, hide it!')
195+
196+
local message = bint.frombe("Hello world!")
197+
print('Message: '.. message:tobe(true))
198+
199+
-- Sign message
200+
local sign_public_key, sign_binding_factor = sign(message, sign_private_key, private_key)
201+
print(string.format('signature = (%s, %s)', sign_public_key, sign_binding_factor))
202+
assert(sign_public_key:valid())
203+
204+
-- Verify message
205+
local ok = verify(message, sign_binding_factor, sign_public_key, public_key)
206+
print('Verification: ' .. tostring(ok))
207+
assert(ok == true)
208+
end
146209

147-
local decrypted_message = decrypt(encrypted_message, symmetric_public_key, secret_key)
148-
print('Decoded: ' .. decrypted_message:tobe(true))
149-
assert(message == decrypted_message)
150210
print('success!')

0 commit comments

Comments
 (0)