Last active
December 31, 2015 17:07
-
-
Save jl2012/760b0f952715b8b6c608 to your computer and use it in GitHub Desktop.
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
#!/usr/bin/python | |
# General segwit address by jl2012 2015 (Public domain) | |
# Damm checksum code by Ilmari Karonen | |
# reduction bitmasks for GF(2^n), 2 <= n <= 32 | |
masks = (3, 3, 3, 5, 3, 3, 27, 3, 9, 5, 9, 27, 33, 3, 43, 9, | |
9, 39, 9, 5, 3, 33, 27, 9, 27, 39, 3, 5, 3, 9, 141) | |
#base32 = "0123456789ABCDEFGHJKMNPQRSTUVWXY" | |
base32 = "ybndrfg8ejkmcpqxot1uwisza345h769" | |
# calculate Damm checksum for base 2^n | |
def damm2n (digits, n): | |
modulus = (1 << n) | |
mask = modulus | masks[n - 2] | |
checksum = 0 | |
for digit in digits: | |
checksum ^= digit | |
checksum <<= 1 | |
if checksum >= modulus: checksum ^= mask | |
return checksum | |
# transform version byte and witness program to base32 | |
def wpto32 (vb, wp): | |
b32 = "" | |
binary = bin(int(vb))[2:].zfill(4) + bin(int(wp,16))[2:].zfill(256) | |
while (len(binary) > 0): | |
value = int(binary[:5],2) | |
b32 += list(base32)[value] | |
binary = binary[5:] | |
return b32 | |
# return Damm checksum for a list of base32 characters | |
def base32damm (b32digits): | |
decdigits = [] | |
for b32digit in b32digits: | |
decdigits.append(base32.find(str(b32digit))) | |
decdamm = damm2n (decdigits, 5) | |
return list(base32)[decdamm] | |
# calculate first round of checksum | |
def firstround (rawadd1): | |
r1 = list(rawadd1) | |
for pos in [48, 42, 36, 30, 24, 18, 12, 6, 0]: | |
r1.insert(pos+6, base32damm ([r1[pos], r1[pos+1], r1[pos+2], r1[pos+3], r1[pos+4], r1[pos+5]])) | |
return ''.join(r1) | |
# calculate second round of checksum | |
def secondround (rawadd2): | |
r2 = list(rawadd2) | |
for pos in [0, 1, 2, 3, 4, 5]: | |
r2.append(base32damm ([r2[pos], r2[pos+7], r2[pos+14], r2[pos+21], r2[pos+28], r2[pos+35], r2[pos+42], r2[pos+49], r2[pos+56]])) | |
return ''.join(r2) | |
# base32 to hex | |
def b32tohex (b32): | |
binary = "" | |
returnhex = "" | |
for b32digit in b32: | |
binary += bin(base32.find(str(b32digit)))[2:].zfill(5) | |
# padding to the nearest multiple of 8 | |
binary = binary.zfill(( (len(binary) - 1) / 8 * 8) + 8) | |
while (len(binary) > 0): | |
value = int(binary[:8],2) | |
returnhex += hex(value)[2:].zfill(2) | |
binary = binary[8:] | |
return returnhex | |
def wronginput (): | |
print "\nSyntax\n" | |
print "Encode: segwit_address.py -e <version> <witness program>" | |
print "Decode: segwit_address.py -d <address>" | |
print "" | |
sys.exit | |
import sys | |
import re | |
if len(sys.argv) < 2: | |
wronginput() | |
elif (str(sys.argv[1]) == "-e") & (len(sys.argv) == 4): | |
patternv = re.compile("^(1[0-5]|[0-9])$") | |
if not patternv.match(sys.argv[2]): | |
sys.exit("The witness programe version must be within 0 to 15") | |
patternw = re.compile("^([A-Fa-f0-9][A-Fa-f0-9]){2,32}$") | |
if not patternw.match(sys.argv[3]): | |
sys.exit("The witness program must be a hex string of 2 to 32 bytes") | |
vbyte = sys.argv[2] | |
witprog = sys.argv[3] | |
witproglen = len(witprog)/2 | |
witprog32 = wpto32 (vbyte, witprog) | |
rawaddress = list(base32)[1] + list(base32)[witproglen-1] + witprog32 | |
firstroundresult = firstround (rawaddress) | |
final = secondround (firstroundresult) | |
rawtest = list(base32)[17] + list(base32)[witproglen-1] + witprog32 | |
firstroundtest = firstround (rawtest) | |
finaltest = secondround (firstroundtest) | |
print "" | |
print "Witness program:\t" + witprog + " (verion " + str(vbyte) + ", " + str(witproglen) + " bytes)" | |
print "Wit ver and prog in b32:" + witprog32 | |
print "" | |
print "Mainnet:" | |
print "Add version and length:\t" + (" -".join(rawaddress[i:i+6] for i in range(0, len(rawaddress),6))) | |
print "First round checksum: \t" + ("-".join(firstroundresult[i:i+7] for i in range(0, len(firstroundresult),7))) | |
print "Second round checksum: \t" + ("-".join(final[i:i+7] for i in range(0, len(final),7))) | |
print "" | |
print "Testnet:" | |
print "Add version and length:\t" + (" -".join(rawtest[i:i+6] for i in range(0, len(rawtest),6))) | |
print "First round checksum: \t" + ("-".join(firstroundtest[i:i+7] for i in range(0, len(firstroundtest),7))) | |
print "Second round checksum: \t" + ("-".join(finaltest[i:i+7] for i in range(0, len(finaltest),7))) | |
print "" | |
elif (str(sys.argv[1]) == "-d") & (len(sys.argv) == 3): | |
# Remove hyphens and basic checks | |
ad = sys.argv[2].replace('-','').lower() | |
if not (len(ad) == 69): | |
sys.exit("Address must be 69 digits") | |
pattern = re.compile("^([13-9a-km-uw-z]+)$") | |
if not pattern.match(ad): | |
sys.exit("Invalid digits in address") | |
error = "" | |
al = list(ad) | |
cs1error = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] | |
for pos in [0, 7, 14, 21, 28, 35, 42, 49, 56]: | |
if not (al[pos+6] == base32damm ([al[pos], al[pos+1], al[pos+2], al[pos+3], al[pos+4], al[pos+5]])): | |
cs1error[pos/7] = 1 | |
error = 1 | |
cs2error = [0, 0, 0, 0, 0, 0, 0] | |
for pos in [0, 1, 2, 3, 4, 5]: | |
if not (al[pos+63] == base32damm ([al[pos], al[pos+7], al[pos+14], al[pos+21], al[pos+28], al[pos+35], al[pos+42], al[pos+49], al[pos+56]])): | |
cs2error[pos] = 1 | |
error = 1 | |
print "" | |
print "Address: \t" + ("-".join(ad[i:i+7] for i in range(0, len(ad),7))) | |
if (error): | |
errorstring = "" | |
for c1 in cs1error: | |
for c2 in cs2error: | |
if (c1 & c2): | |
errorstring += "*" | |
elif (c1 | c2): | |
errorstring += "^" | |
else: | |
errorstring += " " | |
print "Possible error: " + (" ".join(errorstring[i:i+7] for i in range(0, len(errorstring),7))) | |
print "Checksum mismatch" | |
else: | |
# Remove checksum | |
for delete in [68, 67, 66, 65, 64, 63, 62, 55, 48, 41, 34, 27, 20, 13, 6]: | |
del al[delete] | |
# Network is the 1st b32 digit | |
if (al[0] == "b"): | |
net = "Mainnet" | |
elif (al[0] == "t"): | |
net = "Testnet" | |
else: | |
net = "Unknown" | |
# Program length = 2nd b32 digit + 1 | |
length = base32.find(str(al[1])) + 1 | |
# Program version is the first 4 bits of the 3rd b32 digit | |
version = base32.find(str(al[2])) >> 1 | |
# Program version to push opcode | |
if (version == 0): | |
versionbyte = "00" | |
else: | |
versionbyte = hex(version+80)[2:] | |
proglenbyte = hex(length)[2:].zfill(2) | |
proghex = b32tohex(al)[(0-(length*2)):] | |
scriptpubkey = versionbyte + proglenbyte + proghex | |
print "Network:\t" + net | |
print "Program length:\t" + str(length) + " bytes" | |
print "Program ver:\t" + str(version) | |
print "scriptPubKey:\t" + scriptpubkey | |
if (version == 1) & (length != 32): | |
print "\nWarning!!! This is an invalid version 1 witness program. Bitcoin sent to this address is not spendable." | |
if (version > 1): | |
print "\nWarning!!! The witness program version is unknown. Are you sure you want to send bitcoin to this address?" | |
print "" | |
else: | |
wronginput() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment