Skip to content

Instantly share code, notes, and snippets.

@jl2012
Last active December 31, 2015 17:07
Show Gist options
  • Save jl2012/760b0f952715b8b6c608 to your computer and use it in GitHub Desktop.
Save jl2012/760b0f952715b8b6c608 to your computer and use it in GitHub Desktop.
#!/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