├── .gitignore ├── README.md └── signmessage.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sign Message 2 | =========== 3 | 4 | A single purpose small Python script to sign and verify messages with bitcoin private keys. 5 | 6 | 7 | #### How to use: 8 | 9 | Requires ecdsa library. Do `pip install ecdsa` before using script. 10 | 11 | ###### To sign a message: 12 | $./signmessage.py -s 13 | Verify message 14 | 15 | Enter address: 16 | 1HUBHMij46Hae75JPdWjeZ5Q7KaL7EFRSD 17 | Enter message: 18 | test message 19 | Enter private key: 20 | 5KMWWy2d3Mjc8LojNoj8Lcz9B1aWu8bRofUgGwQk959Dw5h2iyw 21 | 22 | Signature: 23 | 24 | G0k+Nt1u5boTTUfLyj6x1T5flg1v9rUKGlhs/jPApaTWLHf3GVdAIOIHip6sVwXEuzQGPWIlS0VT+yryXiDaavw= 25 | 26 | 27 | ###### To sign a message: 28 | $./signmessage.py -s 29 | Verify message 30 | 31 | Enter address: 32 | 14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY 33 | Enter message: 34 | test message 35 | Enter private key: 36 | L41XHGJA5QX43QRG3FEwPbqD5BYvy6WxUxqAMM9oQdHJ5FcRHcGk 37 | 38 | Signature: 39 | 40 | IPn9bbEdNUp6+bneZqE2YJbq9Hv5aNILq9E5eZoMSF3/fBX4zjeIN6fpXfGSGPrZyKfHQ/c/kTSP+NIwmyTzMfk= 41 | 42 | 43 | ###### To verify a message: 44 | $./signmessage.py 45 | Verify message 46 | 47 | Enter address: 48 | 14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY 49 | Enter message: 50 | test message 51 | Enter signature: 52 | IPn9bbEdNUp6+bneZqE2YJbq9Hv5aNILq9E5eZoMSF3/fBX4zjeIN6fpXfGSGPrZyKfHQ/c/kTSP+NIwmyTzMfk= 53 | 54 | Message verified: True 55 | -------------------------------------------------------------------------------- /signmessage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # the code below is 'borrowed' almost verbatim from electrum, 4 | # https://gitorious.org/electrum/electrum 5 | # and is under the GPLv3. 6 | 7 | import ecdsa 8 | import base64 9 | import hashlib 10 | from ecdsa.util import string_to_number 11 | import sys 12 | 13 | VERBOSE = False 14 | #VERBOSE = True 15 | 16 | # secp256k1, http://www.oid-info.com/get/1.3.132.0.10 17 | _p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL 18 | _r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L 19 | _b = 0x0000000000000000000000000000000000000000000000000000000000000007L 20 | _a = 0x0000000000000000000000000000000000000000000000000000000000000000L 21 | _Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L 22 | _Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L 23 | curve_secp256k1 = ecdsa.ellipticcurve.CurveFp( _p, _a, _b ) 24 | generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r ) 25 | oid_secp256k1 = (1,3,132,0,10) 26 | SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 ) 27 | 28 | addrtype = 0 29 | 30 | # from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/ 31 | 32 | def modular_sqrt(a, p): 33 | """ Find a quadratic residue (mod p) of 'a'. p 34 | must be an odd prime. 35 | 36 | Solve the congruence of the form: 37 | x^2 = a (mod p) 38 | And returns x. Note that p - x is also a root. 39 | 40 | 0 is returned is no square root exists for 41 | these a and p. 42 | 43 | The Tonelli-Shanks algorithm is used (except 44 | for some simple cases in which the solution 45 | is known from an identity). This algorithm 46 | runs in polynomial time (unless the 47 | generalized Riemann hypothesis is false). 48 | """ 49 | # Simple cases 50 | # 51 | if legendre_symbol(a, p) != 1: 52 | return 0 53 | elif a == 0: 54 | return 0 55 | elif p == 2: 56 | return p 57 | elif p % 4 == 3: 58 | return pow(a, (p + 1) / 4, p) 59 | 60 | # Partition p-1 to s * 2^e for an odd s (i.e. 61 | # reduce all the powers of 2 from p-1) 62 | # 63 | s = p - 1 64 | e = 0 65 | while s % 2 == 0: 66 | s /= 2 67 | e += 1 68 | 69 | # Find some 'n' with a legendre symbol n|p = -1. 70 | # Shouldn't take long. 71 | # 72 | n = 2 73 | while legendre_symbol(n, p) != -1: 74 | n += 1 75 | 76 | # Here be dragons! 77 | # Read the paper "Square roots from 1; 24, 51, 78 | # 10 to Dan Shanks" by Ezra Brown for more 79 | # information 80 | # 81 | 82 | # x is a guess of the square root that gets better 83 | # with each iteration. 84 | # b is the "fudge factor" - by how much we're off 85 | # with the guess. The invariant x^2 = ab (mod p) 86 | # is maintained throughout the loop. 87 | # g is used for successive powers of n to update 88 | # both a and b 89 | # r is the exponent - decreases with each update 90 | # 91 | x = pow(a, (s + 1) / 2, p) 92 | b = pow(a, s, p) 93 | g = pow(n, s, p) 94 | r = e 95 | 96 | while True: 97 | t = b 98 | m = 0 99 | for m in xrange(r): 100 | if t == 1: 101 | break 102 | t = pow(t, 2, p) 103 | 104 | if m == 0: 105 | return x 106 | 107 | gs = pow(g, 2 ** (r - m - 1), p) 108 | g = (gs * gs) % p 109 | x = (x * gs) % p 110 | b = (b * g) % p 111 | r = m 112 | 113 | def legendre_symbol(a, p): 114 | """ Compute the Legendre symbol a|p using 115 | Euler's criterion. p is a prime, a is 116 | relatively prime to p (if p divides 117 | a, then a|p = 0) 118 | 119 | Returns 1 if a has a square root modulo 120 | p, -1 otherwise. 121 | """ 122 | ls = pow(a, (p - 1) / 2, p) 123 | return -1 if ls == p - 1 else ls 124 | 125 | __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' 126 | __b58base = len(__b58chars) 127 | 128 | def b58encode(v): 129 | """ encode v, which is a string of bytes, to base58. 130 | """ 131 | 132 | long_value = 0L 133 | for (i, c) in enumerate(v[::-1]): 134 | long_value += (256**i) * ord(c) 135 | 136 | result = '' 137 | while long_value >= __b58base: 138 | div, mod = divmod(long_value, __b58base) 139 | result = __b58chars[mod] + result 140 | long_value = div 141 | result = __b58chars[long_value] + result 142 | 143 | # Bitcoin does a little leading-zero-compression: 144 | # leading 0-bytes in the input become leading-1s 145 | nPad = 0 146 | for c in v: 147 | if c == '\0': nPad += 1 148 | else: break 149 | 150 | return (__b58chars[0]*nPad) + result 151 | 152 | def b58decode(v, length): 153 | """ decode v into a string of len bytes.""" 154 | long_value = 0L 155 | for (i, c) in enumerate(v[::-1]): 156 | long_value += __b58chars.find(c) * (__b58base**i) 157 | 158 | result = '' 159 | while long_value >= 256: 160 | div, mod = divmod(long_value, 256) 161 | result = chr(mod) + result 162 | long_value = div 163 | result = chr(long_value) + result 164 | 165 | nPad = 0 166 | for c in v: 167 | if c == __b58chars[0]: nPad += 1 168 | else: break 169 | 170 | result = chr(0)*nPad + result 171 | if length is not None and len(result) != length: 172 | return None 173 | 174 | return result 175 | 176 | def msg_magic(message): 177 | return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message 178 | 179 | def Hash(data): 180 | return hashlib.sha256(hashlib.sha256(data).digest()).digest() 181 | 182 | def hash_160(public_key): 183 | md = hashlib.new('ripemd160') 184 | md.update(hashlib.sha256(public_key).digest()) 185 | return md.digest() 186 | 187 | def hash_160_to_bc_address(h160): 188 | vh160 = chr(addrtype) + h160 189 | h = Hash(vh160) 190 | addr = vh160 + h[0:4] 191 | return b58encode(addr) 192 | 193 | def public_key_to_bc_address(public_key): 194 | h160 = hash_160(public_key) 195 | return hash_160_to_bc_address(h160) 196 | 197 | def encode_point(pubkey, compressed=False): 198 | order = generator_secp256k1.order() 199 | p = pubkey.pubkey.point 200 | x_str = ecdsa.util.number_to_string(p.x(), order) 201 | y_str = ecdsa.util.number_to_string(p.y(), order) 202 | if compressed: 203 | return chr(2 + (p.y() & 1)) + x_str 204 | else: 205 | return chr(4) + x_str + y_str 206 | 207 | def sign_message(private_key, message, compressed=False): 208 | public_key = private_key.get_verifying_key() 209 | signature = private_key.sign_digest( Hash( msg_magic( message ) ), sigencode = ecdsa.util.sigencode_string ) 210 | address = public_key_to_bc_address(encode_point(public_key, compressed)) 211 | assert public_key.verify_digest( signature, Hash( msg_magic( message ) ), sigdecode = ecdsa.util.sigdecode_string) 212 | for i in range(4): 213 | nV = 27 + i 214 | if compressed: 215 | nV += 4 216 | sig = base64.b64encode( chr(nV) + signature ) 217 | try: 218 | if verify_message( address, sig, message): 219 | return sig 220 | except: 221 | continue 222 | else: 223 | raise BaseException("error: cannot sign message") 224 | 225 | def verify_message(address, signature, message): 226 | """ See http://www.secg.org/download/aid-780/sec1-v2.pdf for the math """ 227 | from ecdsa import numbertheory, ellipticcurve, util 228 | curve = curve_secp256k1 229 | G = generator_secp256k1 230 | order = G.order() 231 | # extract r,s from signature 232 | sig = base64.b64decode(signature) 233 | if len(sig) != 65: raise BaseException("Wrong encoding") 234 | r,s = util.sigdecode_string(sig[1:], order) 235 | nV = ord(sig[0]) 236 | if nV < 27 or nV >= 35: 237 | return False 238 | if nV >= 31: 239 | compressed = True 240 | nV -= 4 241 | else: 242 | compressed = False 243 | recid = nV - 27 244 | # 1.1 245 | x = r + (recid/2) * order 246 | # 1.3 247 | alpha = ( x * x * x + curve.a() * x + curve.b() ) % curve.p() 248 | beta = modular_sqrt(alpha, curve.p()) 249 | y = beta if (beta - recid) % 2 == 0 else curve.p() - beta 250 | # 1.4 the constructor checks that nR is at infinity 251 | R = ellipticcurve.Point(curve, x, y, order) 252 | # 1.5 compute e from message: 253 | h = Hash( msg_magic( message ) ) 254 | e = string_to_number(h) 255 | minus_e = -e % order 256 | # 1.6 compute Q = r^-1 (sR - eG) 257 | inv_r = numbertheory.inverse_mod(r,order) 258 | Q = inv_r * ( s * R + minus_e * G ) 259 | public_key = ecdsa.VerifyingKey.from_public_point( Q, curve = SECP256k1 ) 260 | # check that Q is the public key 261 | public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string) 262 | # check that we get the original signing address 263 | addr = public_key_to_bc_address(encode_point(public_key, compressed)) 264 | if address == addr: 265 | return True 266 | else: 267 | #print addr 268 | return False 269 | 270 | 271 | def sign_message_with_secret(secret, message, compressed=False): 272 | private_key = ecdsa.SigningKey.from_secret_exponent( secret, curve = SECP256k1 ) 273 | 274 | public_key = private_key.get_verifying_key() 275 | signature = private_key.sign_digest( Hash( msg_magic( message ) ), sigencode = ecdsa.util.sigencode_string ) 276 | address = public_key_to_bc_address(encode_point(public_key, compressed)) 277 | if VERBOSE: print 'address:\n', address 278 | assert public_key.verify_digest( signature, Hash( msg_magic( message ) ), sigdecode = ecdsa.util.sigdecode_string) 279 | for i in range(4): 280 | nV = 27 + i 281 | if compressed: 282 | nV += 4 283 | sig = base64.b64encode( chr(nV) + signature ) 284 | try: 285 | if verify_message( address, sig, message): 286 | return sig 287 | except: 288 | continue 289 | else: 290 | raise BaseException("error: cannot sign message") 291 | 292 | 293 | def sign_message_with_private_key(base58_priv_key, message, compressed=True): 294 | encoded_priv_key_bytes = b58decode(base58_priv_key, None) 295 | encoded_priv_key_hex_string = encoded_priv_key_bytes.encode('hex') 296 | 297 | secret_hex_string = '' 298 | if base58_priv_key[0] == 'L' or base58_priv_key[0] == 'K': 299 | assert len(encoded_priv_key_hex_string) == 76 300 | # strip leading 0x08, 0x01 compressed flag, checksum 301 | secret_hex_string = encoded_priv_key_hex_string[2:-10] 302 | elif base58_priv_key[0] == '5': 303 | assert len(encoded_priv_key_hex_string) == 74 304 | # strip leading 0x08 and checksum 305 | secret_hex_string = encoded_priv_key_hex_string[2:-8] 306 | else: 307 | raise BaseException("error: private must start with 5 if uncompressed or L/K for compressed") 308 | 309 | if VERBOSE: print 'secret_hex_string:\n', secret_hex_string 310 | secret = int(secret_hex_string, 16) 311 | 312 | checksum = Hash(encoded_priv_key_bytes[:-4])[:4].encode('hex') 313 | if VERBOSE: print 'checksum:\n', checksum 314 | assert checksum == encoded_priv_key_hex_string[-8:] #make sure private key is valid 315 | if VERBOSE: print 'secret:\n', secret 316 | return sign_message_with_secret(secret, message, compressed) 317 | 318 | 319 | def sign_and_verify(wifPrivateKey, message, bitcoinaddress, compressed=True): 320 | sig = sign_message_with_private_key(wifPrivateKey, message, compressed) 321 | assert verify_message(bitcoinaddress, sig, message) 322 | if VERBOSE: print 'verify_message:', verify_message(bitcoinaddress, sig, message) 323 | return sig 324 | 325 | 326 | def test_sign_messages(): 327 | wif1 = '5KMWWy2d3Mjc8LojNoj8Lcz9B1aWu8bRofUgGwQk959Dw5h2iyw' 328 | compressedPrivKey1 = 'L41XHGJA5QX43QRG3FEwPbqD5BYvy6WxUxqAMM9oQdHJ5FcRHcGk' 329 | addressUncompressesed1 = '1HUBHMij46Hae75JPdWjeZ5Q7KaL7EFRSD' 330 | addressCompressesed1 = '14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY' 331 | msg1 = 'test message' 332 | print 'sig:\n', sign_and_verify(wif1, msg1, addressUncompressesed1, False) # good 333 | print 'sig:\n', sign_and_verify(wif1, msg1, addressCompressesed1) # good 334 | #print 'sig:\n', sign_and_verify(wif1, msg1, addressUncompressesed1) # bad 335 | #print 'sig:\n', sign_and_verify(wif1, msg1, addressCompressesed1, False) # bad 336 | 337 | print 'sig:\n', sign_and_verify(compressedPrivKey1, msg1, addressCompressesed1) # good 338 | print 'sig:\n', sign_and_verify(compressedPrivKey1, msg1, addressUncompressesed1, False) # good 339 | #print 'sig:\n', sign_and_verify(compressedPrivKey1, msg1, addressUncompressesed1) # bad 340 | #print 'sig:\n', sign_and_verify(compressedPrivKey1, msg1, addressCompressesed1, False) # bad 341 | 342 | 343 | def sign_input_message(): 344 | print 'Sign message\n' 345 | address = raw_input("Enter address:\n") 346 | message = raw_input("Enter message:\n") 347 | base58_priv_key = raw_input("Enter private key:\n") 348 | 349 | """ 350 | address = '14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY' 351 | message = 'test message' 352 | base58_priv_key = 'L41XHGJA5QX43QRG3FEwPbqD5BYvy6WxUxqAMM9oQdHJ5FcRHcGk' 353 | #""" 354 | 355 | compressed = True 356 | if base58_priv_key[0] == 'L' or base58_priv_key[0] == 'K': 357 | compressed = True 358 | elif base58_priv_key[0] == '5': 359 | compressed = False 360 | else: 361 | raise BaseException("error: private must start with 5 if uncompressed or L/K for compressed") 362 | 363 | print '\n\n\n' 364 | print address 365 | print message 366 | print base58_priv_key 367 | print 'Signature:\n\n', sign_and_verify(base58_priv_key, message, address, compressed) 368 | 369 | 370 | def verify_input_message(): 371 | print 'Verify message\n' 372 | address = raw_input("Enter address:\n") 373 | message = raw_input("Enter message:\n") 374 | signature = raw_input("Enter signature:\n") 375 | 376 | """ 377 | address = '14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY' 378 | message = 'test message' 379 | signature = 'IPn9bbEdNUp6+bneZqE2YJbq9Hv5aNILq9E5eZoMSF3/fBX4zjeIN6fpXfGSGPrZyKfHQ/c/kTSP+NIwmyTzMfk=' 380 | #""" 381 | 382 | print '\n\n\n' 383 | print address 384 | print message 385 | print signature 386 | print 'Message verified:', verify_message(address, signature, message) 387 | 388 | def main(): 389 | argv = sys.argv 390 | if len(argv) > 1 and argv[1] == '-s': 391 | sign_input_message() 392 | else: 393 | verify_input_message() 394 | 395 | 396 | if __name__ == '__main__': 397 | #test_sign_messages() 398 | main() 399 | --------------------------------------------------------------------------------