Using the constraints and computations specified in the binary, we'll write a Z3 script to give us our good serial.
# Imports
import z3
# Initialising required variables
# Z3's solver engine
s = z3.Solver()
We have to create an array such that operations could be done on it for the first transformation loop.
# Declare a bitvector variable
x = z3.BitVec('x', 8)
# 'sexpr' method shows the internal representation.
(x + 2).sexpr()
Which translates to: bit vector addition of x with the integral value 2.
# Input serial array of length 13, since the serial length is 13.
serial = []
for i in range(13):
serial.append(z3.BitVec("serial_{}".format(i), 32))
serial
# v4 initialised with 3.
running_sum = [3]
Implementing constraints for the value boundations on every character in the input.
for digit in serial:
s.add(z3.And(digit>48, digit<=57))
Check value of running sum at every iteration.
# Append sum to a running sums array, and add it as a constraint at every interation.
for i in range((len(serial)-1)):
current_sum = z3.BitVec("sum_{}".format(i), 32)
s.add(current_sum==running_sum[-1]+((2*running_sum[-1])^(serial[i]-48)))
running_sum.append(current_sum)
running_sum[-1]
Constraint on the last digit of the serial.
s.add(serial[-1]==(running_sum[-1]%10+48))
Check if the problem is satisfiable.
s.check()
if s.check() == z3.sat:
m = s.model()
m
Cleaning up and representing the answer in a readable form:
# Now m is a map. We dereference the values of serial required from it.
print("Serial object:", serial)
inp = []
for digit in serial:
# 'as_long' method used to convert vector value to long type.
print("Value of", digit, "is:", m[digit])
inp.append(m[digit].as_long())
inp
These bytes give you the value of the good serial. Convert the ordinal values to their ASCII equivalents.
solution = "".join([chr(x) for x in inp])
solution