You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

142 lines
5.2 KiB

import struct
import base64
import json
from jmbitcoin import ecdsa_sign, ecdsa_verify
import binascii
def assert_is_utxo(utxo):
assert len(utxo) == 2
assert isinstance(utxo[0], bytes)
assert len(utxo[0]) == 32
assert isinstance(utxo[1], int)
assert utxo[1] >= 0
def get_cert_msg(cert_pub, cert_expiry):
return b'fidelity-bond-cert|' + cert_pub + b'|' + str(cert_expiry).encode('ascii')
def get_ascii_cert_msg(cert_pub, cert_expiry):
return b'fidelity-bond-cert|' + binascii.hexlify(cert_pub) + b'|' + str(cert_expiry).encode('ascii')
class FidelityBond:
def __init__(self, utxo, utxo_pubkey, locktime, cert_expiry,
cert_privkey, cert_pubkey, cert_signature):
assert_is_utxo(utxo)
assert isinstance(utxo_pubkey, bytes)
assert isinstance(locktime, int)
assert isinstance(cert_expiry, int)
assert isinstance(cert_privkey, bytes)
assert isinstance(cert_pubkey, bytes)
assert isinstance(cert_signature, bytes)
self.utxo = utxo
self.utxo_pubkey = utxo_pubkey
self.locktime = locktime
self.cert_expiry = cert_expiry
self.cert_privkey = cert_privkey
self.cert_pubkey = cert_pubkey
self.cert_signature = cert_signature
def create_proof(self, maker_nick, taker_nick):
return FidelityBondProof(
maker_nick, taker_nick, self.cert_pubkey, self.cert_expiry,
self.cert_signature, self.utxo, self.utxo_pubkey, self.locktime)
def serialize(self):
return json.dumps([
self.utxo,
self.utxo_pubkey,
self.locktime,
self.cert_expiry,
self.cert_privkey,
self.cert_pubkey,
self.cert_signature,
])
@classmethod
def deserialize(cls, data):
return cls(*json.loads(data))
class FidelityBondProof:
# nick_sig + cert_sig + cert_pubkey + cert_expiry + utxo_pubkey + txid + vout + timelock
# 72 + 72 + 33 + 2 + 33 + 32 + 4 + 4 = 252 bytes
SER_STUCT_FMT = '<72s72s33sH33s32sII'
def __init__(self, maker_nick, taker_nick, cert_pub, cert_expiry,
cert_sig, utxo, utxo_pub, locktime):
assert isinstance(maker_nick, str)
assert isinstance(taker_nick, str)
assert isinstance(cert_pub, bytes)
assert isinstance(cert_sig, bytes)
assert isinstance(utxo_pub, bytes)
assert isinstance(locktime, int)
assert_is_utxo(utxo)
self.maker_nick = maker_nick
self.taker_nick = taker_nick
self.cert_pub = cert_pub
self.cert_expiry = cert_expiry
self.cert_sig = cert_sig
self.utxo = utxo
self.utxo_pub = utxo_pub
self.locktime = locktime
@property
def nick_msg(self):
return (self.taker_nick + '|' + self.maker_nick).encode('ascii')
def create_proof_msg(self, cert_priv):
nick_sig = ecdsa_sign(self.nick_msg, cert_priv)
# FIXME: remove stupid base64
nick_sig = base64.b64decode(nick_sig)
return self._serialize_proof_msg(nick_sig)
def _serialize_proof_msg(self, msg_signature):
msg_signature = msg_signature.rjust(72, b'\xff')
cert_sig = self.cert_sig.rjust(72, b'\xff')
fidelity_bond_data = struct.pack(
self.SER_STUCT_FMT,
msg_signature,
cert_sig,
self.cert_pub,
self.cert_expiry,
self.utxo_pub,
self.utxo[0],
self.utxo[1],
self.locktime
)
return base64.b64encode(fidelity_bond_data).decode('ascii')
@staticmethod
def _verify_signature(message, signature, pubkey):
# FIXME: remove stupid base64
return ecdsa_verify(message, base64.b64encode(signature), pubkey)
@classmethod
def parse_and_verify_proof_msg(cls, maker_nick, taker_nick, data):
try:
decoded_data = base64.b64decode(data, validate=True)
except binascii.Error:
raise ValueError("decode error")
if len(decoded_data) != 252:
raise ValueError("invalid length")
unpacked_data = struct.unpack(cls.SER_STUCT_FMT, decoded_data)
try:
signature = unpacked_data[0][unpacked_data[0].index(b'\x30'):]
cert_sig = unpacked_data[1][unpacked_data[1].index(b'\x30'):]
except ValueError:
#raised if index() doesnt find the position
raise ValueError("der signature header not found")
proof = cls(maker_nick, taker_nick, unpacked_data[2], unpacked_data[3],
cert_sig, (unpacked_data[5], unpacked_data[6]),
unpacked_data[4], unpacked_data[7])
cert_msg = get_cert_msg(proof.cert_pub, proof.cert_expiry)
ascii_cert_msg = get_ascii_cert_msg(proof.cert_pub, proof.cert_expiry)
if not cls._verify_signature(proof.nick_msg, signature, proof.cert_pub):
raise ValueError("nick sig does not verify")
if not cls._verify_signature(cert_msg, proof.cert_sig, proof.utxo_pub) and\
not cls._verify_signature(ascii_cert_msg, proof.cert_sig, proof.utxo_pub):
raise ValueError("cert sig does not verify")
return proof