def sign_message(self, message, keypath): to_hash = b"" to_hash += self.message_magic to_hash += ser_compact_size(len(message)) to_hash += message hashed_message = hash256(to_hash) to_send = '{"sign":{"data":[{"hash":"' to_send += binascii.hexlify(hashed_message) to_send += '","keypath":"' to_send += keypath to_send += '"}]}}' reply = send_encrypt(to_send, self.password, self.device) print(reply) if 'error' in reply: return print( "Touch the device for 3 seconds to sign. Touch briefly to cancel") reply = send_encrypt(to_send, self.password, self.device) print(reply) if 'error' in reply: return sig = binascii.unhexlify(reply['sign'][0]['sig']) r = sig[0:32] s = sig[32:64] recid = binascii.unhexlify(reply['sign'][0]['recid']) compact_sig = ser_sig_compact(r, s, recid) print(binascii.hexlify(compact_sig)) return json.dumps({"signature": base64.b64encode(compact_sig)})
def make_txn_sighash(self, replace_idx, replacement, sighash_type): # calculate the hash value for one input of current transaction # - blank all script inputs # - except one single tx in, which is provided # - serialize that without witness data # - append SIGHASH_ALL=1 value (LE32) # - sha256 over that fd = self.fd old_pos = fd.tell() rv = tcc.sha256() # version number rv.update(pack('<i', self.txn_version)) # nVersion # inputs rv.update(ser_compact_size(self.num_inputs)) for in_idx, txi in self.input_iter(): if in_idx == replace_idx: assert not self.inputs[in_idx].witness_utxo assert not self.inputs[in_idx].is_segwit assert replacement.scriptSig rv.update(replacement.serialize()) else: txi.scriptSig = b'' rv.update(txi.serialize()) # outputs rv.update(ser_compact_size(self.num_outputs)) for out_idx, txo in self.output_iter(): rv.update(txo.serialize()) # locktime rv.update(pack('<I', self.lock_time)) assert sighash_type == SIGHASH_ALL, "only SIGHASH_ALL supported" # SIGHASH_ALL==1 value rv.update(b'\x01\x00\x00\x00') fd.seek(old_pos) # double SHA256 return tcc.sha256(rv.digest()).digest()
def write(self, out_fd, ktype, val, key=b''): # serialize helper: write w/ size and key byte out_fd.write(ser_compact_size(1 + len(key))) out_fd.write(bytes([ktype]) + key) if isinstance(val, tuple): (pos, ll) = val out_fd.write(ser_compact_size(ll)) self.fd.seek(pos) while ll: t = self.fd.read(min(64, ll)) out_fd.write(t) ll -= len(t) elif isinstance(val, list): # for subpaths lists (LE32 ints) assert ktype in (PSBT_IN_BIP32_DERIVATION, PSBT_OUT_BIP32_DERIVATION) out_fd.write(ser_compact_size(len(val) * 4)) for i in val: out_fd.write(pack('<I', i)) else: out_fd.write(ser_compact_size(len(val))) out_fd.write(val)
def hash_message(cls, msg=None, msg_len=0): # Perform sha256 for message-signing purposes (only) # - or get setup for that, if msg == None s = tcc.sha256() s.update(cls.msg_signing_prefix()) msg_len = msg_len or len(msg) s.update(ser_compact_size(msg_len)) if msg is None: return s s.update(msg) return tcc.sha256(s.digest()).digest()
def serialize(self, out_fd, upgrade_txn=False): # Ouput into a file. wr = lambda *a: self.write(out_fd, *a) out_fd.write(b'psbt\xff') if upgrade_txn and self.is_complete(): # write out the ready-to-transmit txn # - means we are also a combiner in this case # - hard tho, due to variable length data. # - XXX probably a bad idea, so disabled for now out_fd.write( b'\x01\x00') # keylength=1, key=b'', PSBT_GLOBAL_UNSIGNED_TX with SizerFile() as fd: self.finalize(fd) txn_len = fd.tell() out_fd.write(ser_compact_size(txn_len)) self.finalize(out_fd) else: # provide original txn (unchanged) wr(PSBT_GLOBAL_UNSIGNED_TX, self.txn) for k in self.unknown: wr(k[0], self.unknown[k], k[1:]) # sep between globals in inputs out_fd.write(b'\0') for idx, inp in enumerate(self.inputs): inp.serialize(out_fd, idx) out_fd.write(b'\0') for idx, outp in enumerate(self.outputs): if outp: outp.serialize(out_fd, idx) out_fd.write(b'\0')
def finalize(self, fd): # Stream out the finalized transaction, with signatures applied # - assumption is it's complete already. fd.write(pack('<i', self.txn_version)) # nVersion # does this txn require witness data to be included? # - yes, if the original txn had some # - yes, if we did a segwit signature on any input needs_witness = self.had_witness or any(i.is_segwit for i in self.inputs if i) if needs_witness: # zero marker, and flags=0x01 fd.write(b'\x00\x01') # inputs fd.write(ser_compact_size(self.num_inputs)) for in_idx, txi in self.input_iter(): inp = self.inputs[in_idx] if inp.is_segwit: if inp.is_p2sh: # multisig (p2sh) segwit still requires the script here. txi.scriptSig = inp.scriptSig else: # major win for segwit (p2pkh): no redeem script bloat anymore txi.scriptSig = b'' # NOTE: Actual signature will be in witness data area elif inp.added_sig: # insert the new signature(s) pubkey, der_sig = inp.added_sig s = b'' if not inp.is_multisig: s += ser_push_data(der_sig) s += ser_push_data(pubkey) else: assert False, 'p2sh combining not supported' txi.scriptSig = s fd.write(txi.serialize()) # outputs fd.write(ser_compact_size(self.num_outputs)) for out_idx, txo in self.output_iter(): fd.write(txo.serialize()) if needs_witness: # witness values # - preserve any given ones, add ours for in_idx, wit in self.input_witness_iter(): inp = self.inputs[in_idx] if inp.is_segwit and inp.added_sig: # put in new sig: wit is a CTxInWitness assert not wit.scriptWitness.stack, 'replacing non-empty?' pubkey, der_sig = inp.added_sig if not inp.is_multisig: assert pubkey[0] in { 0x02, 0x03 } and len(pubkey) == 33, "bad v0 pubkey" wit.scriptWitness.stack = [der_sig, pubkey] else: assert False, 'p2sh combining not supported' fd.write(wit.serialize()) # locktime fd.write(pack('<I', self.lock_time))