Skip to content

Instantly share code, notes, and snippets.

@Goatghosts
Created January 24, 2025 00:00
Show Gist options
  • Save Goatghosts/672bbace3dd8f13c60c46cc95fd1aa09 to your computer and use it in GitHub Desktop.
Save Goatghosts/672bbace3dd8f13c60c46cc95fd1aa09 to your computer and use it in GitHub Desktop.
This script generates x-only Schnorr keys on the secp256k1 curve, constructs Kaspa addresses from them, and verifies their correctness through reverse decoding.
PREFIX = "kaspa" # kaspa/kaspatest
VERSION = 0 # PubKey = 0 | PubKeyECDSA = 1 | ScriptHash = 8
CHARSET = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l"
REV_CHARSET = {c: i for i, c in enumerate(CHARSET)}
P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
A = 0x0000000000000000000000000000000000000000000000000000000000000000
B = 0x0000000000000000000000000000000000000000000000000000000000000007
G = [
55066263022277343669578718895168534326250603453777594175500187360389116729240,
32670510020758816978083085130507043184471273380659243275938904335757337482424,
]
def scalar_mult(scalar, point):
current_point = point.copy()
bits = bin(scalar)[2:]
for bit in bits[1:]:
current_point = double_point(current_point)
if bit == "1":
current_point = add_points(current_point, point)
return current_point
def double_point(point):
x, y = point
s = ((3 * x * x + A) * pow(2 * y, P - 2, P)) % P
x3 = (s * s - 2 * x) % P
y3 = (s * (x - x3) - y) % P
return (x3, y3)
def add_points(point1, point2):
if point1 == point2:
return double_point(point1)
x1, y1 = point1
x2, y2 = point2
s = ((y2 - y1) * pow(x2 - x1, P - 2, P)) % P
x3 = (s * s - x1 - x2) % P
y3 = (s * (x1 - x3) - y1) % P
return (x3, y3)
def polymod(values):
c = 1
for d in values:
c0 = c >> 35
c = ((c & 0x07FFFFFFFF) << 5) ^ d
if c0 & 1:
c ^= 0x98F2BC8E61
if c0 & 2:
c ^= 0x79B76D99E2
if c0 & 4:
c ^= 0xF33E5FB3C4
if c0 & 8:
c ^= 0xAE2EABE2A8
if c0 & 16:
c ^= 0x1E4F43E470
return c ^ 1
def checksum(payload, prefix):
arr = [(x & 0x1F) for x in prefix.encode()]
arr.append(0)
arr.extend(payload)
arr.extend([0] * 8)
return polymod(arr)
def conv8to5(data):
bits = 0
current = 0
r = []
for c in data:
current = (current << 8) | c
bits += 8
while bits >= 5:
bits -= 5
r.append((current >> bits) & 31)
if bits > 0:
r.append((current << (5 - bits)) & 31)
return r
def conv5to8(data):
bits = 0
current = 0
r = []
for c in data:
current = (current << 5) | c
bits += 5
while bits >= 8:
bits -= 8
r.append((current >> bits) & 255)
return bytes(r)
def encode_address(prefix, version, payload):
r = conv8to5(bytes([version]) + payload)
c = checksum(r, prefix)
c &= 0xFFFFFFFFFF
chk = conv8to5(c.to_bytes(5, "big"))
return "".join(chr(CHARSET[i]) for i in (r + chk))
def decode_address(prefix, address):
b = []
for ch in address:
val = REV_CHARSET.get(ch.encode()[0], 100)
if val == 100:
raise Exception(f"Decoding error at character '{ch}'")
b.append(val)
if len(b) < 8:
raise Exception("Bad payload")
p, c = b[:-8], b[-8:]
cc = int.from_bytes(conv5to8(c), "big")
if cc != checksum(p, prefix):
raise Exception("Bad checksum")
u = conv5to8(p)
if len(u) < 2:
raise Exception("Bad payload")
return u[0], u[1:]
def xpoint_to_scriptPubKey(xpoint_bytes):
return b"\x00\x00\x20" + xpoint_bytes + b"\xac"
def main():
secret = 40500595084585002008095440525403209228189253202440630095320916245962672666978
print(f'Secret Key: {secret.to_bytes(32, "big").hex()}')
x, y = scalar_mult(secret, G)
x_point = x.to_bytes(32, "big")
print(f"Public X: {x_point.hex()}")
print(f'Public Y: {y.to_bytes(32, "big").hex()}')
scriptPublicKey = xpoint_to_scriptPubKey(x_point).hex()
print(f"scriptPublicKey: {scriptPublicKey}")
encoded_address = encode_address(PREFIX, VERSION, x_point)
print(f"Address: {PREFIX}:{encoded_address}")
recovered_version, recovered_x_point = decode_address(PREFIX, encoded_address)
print(f"Recovered version: {recovered_version}")
print(f"Recovered x-point: {recovered_x_point.hex()}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment