mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-06 10:48:11 +01:00
Decred sighash and signing.
This commit is contained in:
@@ -19,8 +19,91 @@ from basicswap.util.crypto import (
|
||||
ripemd160,
|
||||
)
|
||||
from basicswap.util.extkey import ExtKeyPair
|
||||
from basicswap.util.integer import encode_varint
|
||||
from basicswap.interface.dcr.rpc import make_rpc_func
|
||||
from .messages import CTransaction
|
||||
from .messages import CTransaction, SigHashType, TxSerializeType
|
||||
from .script import push_script_data
|
||||
|
||||
from coincurve.keys import (
|
||||
PrivateKey
|
||||
)
|
||||
|
||||
|
||||
SigHashSerializePrefix: int = 1
|
||||
SigHashSerializeWitness: int = 3
|
||||
|
||||
|
||||
def DCRSignatureHash(sign_script: bytes, hash_type: SigHashType, tx: CTransaction, idx: int) -> bytes:
|
||||
masked_hash_type = hash_type & SigHashType.SigHashMask
|
||||
if masked_hash_type != SigHashType.SigHashAll:
|
||||
raise ValueError('todo')
|
||||
|
||||
# Prefix hash
|
||||
sign_tx_in_idx: int = idx
|
||||
sign_vins = tx.vin
|
||||
if hash_type & SigHashType.SigHashAnyOneCanPay != 0:
|
||||
sign_vins = [tx.vin[idx],]
|
||||
sign_tx_in_idx = 0
|
||||
|
||||
hash_buffer = bytearray()
|
||||
version: int = tx.version | (SigHashSerializePrefix << 16)
|
||||
hash_buffer += version.to_bytes(4, 'little')
|
||||
hash_buffer += encode_varint(len(sign_vins))
|
||||
|
||||
for txi_n, txi in enumerate(sign_vins):
|
||||
hash_buffer += txi.prevout.hash.to_bytes(32, 'little')
|
||||
hash_buffer += txi.prevout.n.to_bytes(4, 'little')
|
||||
hash_buffer += txi.prevout.tree.to_bytes(1)
|
||||
|
||||
# In the case of SigHashNone and SigHashSingle, commit to 0 for everything that is not the input being signed instead.
|
||||
if (masked_hash_type == SigHashType.SigHashNone
|
||||
or masked_hash_type == SigHashType.SigHashSingle) and \
|
||||
sign_tx_in_idx != txi_n:
|
||||
hash_buffer += (0).to_bytes(4, 'little')
|
||||
else:
|
||||
hash_buffer += txi.sequence.to_bytes(4, 'little')
|
||||
|
||||
hash_buffer += encode_varint(len(tx.vout))
|
||||
|
||||
for txo_n, txo in enumerate(tx.vout):
|
||||
if masked_hash_type == SigHashType.SigHashSingle and \
|
||||
idx != txo_n:
|
||||
hash_buffer += (-1).to_bytes(8, 'little')
|
||||
hash_buffer += txo.version.to_bytes(2, 'little')
|
||||
hash_buffer += encode_varint(0)
|
||||
continue
|
||||
hash_buffer += txo.value.to_bytes(8, 'little')
|
||||
hash_buffer += txo.version.to_bytes(2, 'little')
|
||||
hash_buffer += encode_varint(len(txo.script_pubkey))
|
||||
hash_buffer += txo.script_pubkey
|
||||
|
||||
hash_buffer += tx.locktime.to_bytes(4, 'little')
|
||||
hash_buffer += tx.expiry.to_bytes(4, 'little')
|
||||
|
||||
prefix_hash = blake256(hash_buffer)
|
||||
|
||||
# Witness hash
|
||||
hash_buffer.clear()
|
||||
|
||||
version: int = tx.version | (SigHashSerializeWitness << 16)
|
||||
hash_buffer += version.to_bytes(4, 'little')
|
||||
|
||||
hash_buffer += encode_varint(len(sign_vins))
|
||||
for txi_n, txi in enumerate(sign_vins):
|
||||
if sign_tx_in_idx != txi_n:
|
||||
hash_buffer += encode_varint(0)
|
||||
continue
|
||||
hash_buffer += encode_varint(len(sign_script))
|
||||
hash_buffer += sign_script
|
||||
|
||||
witness_hash = blake256(hash_buffer)
|
||||
|
||||
hash_buffer.clear()
|
||||
hash_buffer += hash_type.to_bytes(4, 'little')
|
||||
hash_buffer += prefix_hash
|
||||
hash_buffer += witness_hash
|
||||
|
||||
return blake256(hash_buffer)
|
||||
|
||||
|
||||
class DCRInterface(Secp256k1Interface):
|
||||
@@ -121,7 +204,38 @@ class DCRInterface(Secp256k1Interface):
|
||||
|
||||
return hash160(ek_account.encode_p())
|
||||
|
||||
def decodeKey(self, encoded_key: str) -> (int, bytes):
|
||||
key = b58decode(encoded_key)
|
||||
checksum = key[-4:]
|
||||
key = key[:-4]
|
||||
|
||||
if blake256(key)[:4] != checksum:
|
||||
raise ValueError('Checksum mismatch')
|
||||
return key[2], key[3:]
|
||||
|
||||
def loadTx(self, tx_bytes: bytes) -> CTransaction:
|
||||
tx = CTransaction()
|
||||
tx.deserialize(tx_bytes)
|
||||
return tx
|
||||
|
||||
def signTx(self, key_bytes: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = DCRSignatureHash(prevout_script, SigHashType.SigHashAll, tx, input_n)
|
||||
|
||||
eck = PrivateKey(key_bytes)
|
||||
return eck.sign(sig_hash, hasher=None) + bytes((SigHashType.SigHashAll,))
|
||||
|
||||
def setTxSignature(self, tx_bytes: bytes, stack, txi: int = 0) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
|
||||
script_data = bytearray()
|
||||
for data in stack:
|
||||
push_script_data(script_data, data)
|
||||
|
||||
tx.vin[txi].signature_script = script_data
|
||||
|
||||
return tx.serialize()
|
||||
|
||||
def stripTxSignature(self, tx_bytes) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
return tx.serialize(TxSerializeType.NoWitness)
|
||||
|
||||
@@ -17,14 +17,37 @@ class TxSerializeType(IntEnum):
|
||||
OnlyWitness = 2
|
||||
|
||||
|
||||
class SigHashType(IntEnum):
|
||||
SigHashAll = 0x1
|
||||
SigHashNone = 0x2
|
||||
SigHashSingle = 0x3
|
||||
SigHashAnyOneCanPay = 0x80
|
||||
|
||||
SigHashMask = 0x1f
|
||||
|
||||
|
||||
class SignatureType(IntEnum):
|
||||
STEcdsaSecp256k1 = 0
|
||||
STEd25519 = 1
|
||||
STSchnorrSecp256k1 = 2
|
||||
|
||||
|
||||
class COutpoint:
|
||||
__slots__ = ('hash', 'n', 'tree')
|
||||
|
||||
def get_hash(self) -> bytes:
|
||||
return self.hash.to_bytes(32, 'big')
|
||||
|
||||
|
||||
class CTxIn:
|
||||
__slots__ = ('prevout', 'sequence',
|
||||
'value_in', 'block_height', 'block_index', 'signature_script') # Witness
|
||||
|
||||
def __init__(self, tx=None):
|
||||
self.value_in = -1
|
||||
self.block_height = 0
|
||||
self.block_index = 0xffffffff
|
||||
|
||||
|
||||
class CTxOut:
|
||||
__slots__ = ('value', 'version', 'script_pubkey')
|
||||
@@ -47,7 +70,6 @@ class CTransaction:
|
||||
self.locktime = tx.locktime
|
||||
self.expiry = tx.expiry
|
||||
|
||||
|
||||
def deserialize(self, data: bytes) -> None:
|
||||
|
||||
version = int.from_bytes(data[:4], 'little')
|
||||
@@ -92,6 +114,9 @@ class CTransaction:
|
||||
self.expiry = int.from_bytes(data[o:o + 4], 'little')
|
||||
o += 4
|
||||
|
||||
if ser_type == TxSerializeType.NoWitness:
|
||||
return
|
||||
|
||||
num_wit_scripts, nb = decode_varint(data, o)
|
||||
o += nb
|
||||
|
||||
@@ -140,7 +165,8 @@ class CTransaction:
|
||||
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.OnlyWitness:
|
||||
data += encode_varint(len(self.vin))
|
||||
for txi in self.vin:
|
||||
data += txi.value_in.to_bytes(8, 'little')
|
||||
tc_value_in = txi.value_in & 0xffffffffffffffff # Convert negative values
|
||||
data += tc_value_in.to_bytes(8, 'little')
|
||||
data += txi.block_height.to_bytes(4, 'little')
|
||||
data += txi.block_index.to_bytes(4, 'little')
|
||||
data += encode_varint(len(txi.signature_script))
|
||||
|
||||
39
basicswap/interface/dcr/script.py
Normal file
39
basicswap/interface/dcr/script.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
|
||||
OP_0 = 0x00
|
||||
OP_DATA_1 = 0x01
|
||||
OP_1NEGATE = 0x4f
|
||||
OP_1 = 0x51
|
||||
OP_PUSHDATA1 = 0x4c
|
||||
OP_PUSHDATA2 = 0x4d
|
||||
OP_PUSHDATA4 = 0x4e
|
||||
|
||||
|
||||
def push_script_data(data_array: bytearray, data: bytes) -> None:
|
||||
len_data: int = len(data)
|
||||
|
||||
if len_data == 0 or (len_data == 1 and data[0] == 0):
|
||||
data_array += bytes((OP_0,))
|
||||
return
|
||||
if len_data == 1 and data[0] <= 16:
|
||||
data_array += bytes((OP_1 - 1 + data[0],))
|
||||
return
|
||||
if len_data == 1 and data[0] == 0x81:
|
||||
data_array += bytes((OP_1NEGATE,))
|
||||
return
|
||||
|
||||
if len_data < OP_PUSHDATA1:
|
||||
data_array += bytes(((OP_DATA_1 - 1) + len_data,))
|
||||
elif len_data <= 0xff:
|
||||
data_array += bytes((OP_PUSHDATA1, len_data))
|
||||
elif len_data <= 0xffff:
|
||||
data_array += bytes((OP_PUSHDATA2,)) + len_data.to_bytes(2, 'little')
|
||||
else:
|
||||
data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, 'little')
|
||||
|
||||
data_array += data
|
||||
Reference in New Issue
Block a user