Merge branch 'master' of github.com:diybitcoinhardware/embit
This commit is contained in:
commit
4a110e4735
@ -6,7 +6,9 @@ Should remain minimal to fit in a microcontroller. Also easy to audit.
|
||||
|
||||
Examples can be found in [`examples/`](./examples) folder.
|
||||
|
||||
Micropython-specific tutorial [here](https://github.com/diybitcoinhardware/f469-disco/tree/master/docs/tutorial).
|
||||
Documentation: https://embit.rocks/
|
||||
|
||||
Support the project: `bc1qd4flfrxjctls9ya244u39hd67pcprhvka723gv`
|
||||
|
||||
# Requirements
|
||||
|
||||
|
||||
@ -18,6 +18,12 @@ pub = PublicKey.parse(b"\x02\x01\x8a\x76\x85\x41\xc9\x46...\x85")
|
||||
from embit.transaction import Transaction
|
||||
with open("raw.tx", "rb") as f:
|
||||
tx = Transaction.read_from(f)
|
||||
|
||||
# serialize to bytes
|
||||
sec = pub.serialize()
|
||||
# write to binary file
|
||||
with open("raw2.tx", "wb") as f:
|
||||
tx.write_to(f)
|
||||
```
|
||||
|
||||
Some classes also have string representations. If they don't - we assume hex representation. You can use `.from_string()` and `.to_string()` methods for that, or just call `str(something)`:
|
||||
|
||||
@ -281,7 +281,7 @@ From `PrivateKey` class (available only if internal key is private):
|
||||
- [`sign(msg)`](./ec/private_key.md#sign) - signs a 32-byte message hash and returns ECDSA `Signature`.
|
||||
- [`schnorr_sign(msg)`](./ec/private_key.md#schnorr_sign) - signs a 32-byte message hash and returns `SchnorrSig`.
|
||||
|
||||
From `PublicKey` class:
|
||||
From `PublicKey` class (available with both public and private internal keys):
|
||||
|
||||
- [`sec()`](./ec/public_key.md#sec) - SEC serialization of the public key.
|
||||
- [`xonly()`](./ec/public_key.md#xonly) - x-only serialization of the public key (for taproot).
|
||||
|
||||
@ -1 +1,350 @@
|
||||
# Transactions
|
||||
# Transactions
|
||||
|
||||
This module defines raw Bitcoin transactions as they appear on Bitcoin Blockchain. For the majority of wallet-specific operations it's much more convenient to use [`PSBT`](./psbt.md) that include extra metadata required for signing and parsing.
|
||||
|
||||
The module also includes [`SIGHASH`](#SIGHASH) constants that define what parts of the transaction are covered by the signature when calculating hash for signing.
|
||||
|
||||
Defined classes: [`Transaction`](#Transaction), [`TransactionInput`](#TransactionInput), [`TransactionOutput`](#TransactionOutput).
|
||||
|
||||
All `Transaction*` classes use hex encoding as string representation. They can also be parsed from byte array or byte stream. Read the [basics](README.md#basics) to learn how it works.
|
||||
|
||||
**Examples:**
|
||||
|
||||
Parsing transaction and printing destination addresses:
|
||||
|
||||
```py
|
||||
from embit.transaction import Transaction
|
||||
from embit.networks import NETWORKS
|
||||
|
||||
# using regtest for address display
|
||||
net = NETWORKS["regtest"]
|
||||
|
||||
hextx = "02000000000101a71718b08f7b9a24f6a6251fb0e0aae35376ec0df2a1e2894955f174ac5137820000000000feffffff0271dce316010000001600145af0d1c7f2134d6ffe5fc3079700091e91ee27c440420f00000000001600148464ca4202f52e0d1411dcbf12e97e1709c6379c0247304402207d715b12cd8a92fc25eb06ba5df5cb1345179bddfb31106525186599871f485e022032a523c336ae120d196945855d6ed1de1c8e09d35fc9e0d93e3893fd0493c7d70121024fd5073ee4a67a03878592b339bd01c47f86aa6b4460f28de623d4992339b71200000000"
|
||||
|
||||
# parsing from hex string:
|
||||
tx = Transaction.from_string(hextx)
|
||||
# or using parse method on bytes:
|
||||
raw = bytes.fromhex(hextx)
|
||||
tx = Transaction.parse(raw)
|
||||
|
||||
if tx.is_segwit:
|
||||
print("Segwit transaction")
|
||||
else:
|
||||
print("Legacy transaction")
|
||||
print("txid: %s" % tx.txid().hex())
|
||||
|
||||
print("%d inputs" % len(tx.vin))
|
||||
print("%d outputs" % len(tx.vout))
|
||||
for out in tx.vout:
|
||||
print("%d sats to %s" % (out.value, out.script_pubkey.address(net)))
|
||||
# >> 4678999153 sats to bcrt1qttcdr3ljzdxklljlcvrewqqfr6g7uf7y53mqvf
|
||||
# >> 1000000 sats to bcrt1qs3jv5ssz75hq69q3mjl396t7zuyuvduu4vl66p
|
||||
```
|
||||
|
||||
Manually creating a transaction
|
||||
|
||||
```py
|
||||
from embit.transaction import Transaction, TransactionInput, TransactionOutput
|
||||
from embit.script import p2wpkh, p2sh, Script
|
||||
from embit.ec import PrivateKey
|
||||
|
||||
pk = PrivateKey.from_wif("L4WK9uFXiVRUopgcs41DmThuJdrmnNPX43E9TNhyjsb6Vh2eJcuB")
|
||||
|
||||
# inputs
|
||||
vin = [
|
||||
TransactionInput(
|
||||
txid=bytes.fromhex("823751ac74f1554989e2a1f20dec7653e3aae0b01f25a6f6249a7b8fb01817a7"),
|
||||
vout=0,
|
||||
)
|
||||
]
|
||||
# outputs
|
||||
vout = [
|
||||
# output to nested segwit
|
||||
TransactionOutput(1000_000, p2sh(p2wpkh(pk))),
|
||||
# output from address
|
||||
TransactionOutput(123_456, Script.from_address("bcrt1qttcdr3ljzdxklljlcvrewqqfr6g7uf7y53mqvf"))
|
||||
]
|
||||
tx = Transaction(vin=vin, vout=vout)
|
||||
|
||||
print(tx) # will print hex unsigned transaction we just constructed
|
||||
```
|
||||
|
||||
For signing examples check [Transaction Methods](#Methods).
|
||||
|
||||
# `SIGHASH`
|
||||
|
||||
`SIGHASH` is a collection of constants used for signing. Depending on the sighash different parts of the transactions are covered by the signature. The default sighash used in the majority of transactions is `SIGHASH.ALL` that covers all inputs and outputs of the transactions.
|
||||
|
||||
Available sighashes:
|
||||
|
||||
- `SIGHASH.DEFAULT = 0x00` - a sighash for taproot signatures that covers everything in the transaction and should be omitted during signature serialization. Use it ONLY when creating Schnorr signatures.
|
||||
- `SIGHASH.ALL = 0x01` - a default sighash for all types of transactions, covers everything in the transaction.
|
||||
- `SIGHASH.NONE = 0x02` - very dangerous sighash type, only covers inputs, so outputs of the transaction can be replaced with any other outputs without invalidating the signature. If all inputs use this sighash the miners can steal all the funds by replacing your outputs with miner's outputs.
|
||||
- `SIGHASH.SINGLE = 0x03` - this sighash covers all inputs and only one output that is at the same position as the input. So if input #1 uses this sighash it only requires that output #1 doesn't change, all other outputs can be replaced.
|
||||
- `SIGHASH.ANYONECANPAY = 0x80` - a flag that can be combined with other sighash types and allows adding other inputs to the transaction. For example you can combine `SIGHASH.SINGLE | SIGHASH.ANYONECANPAY` to create a signature that will allow adding other inputs and changing other outputs and only requires that your input and corresponding output remain the same.
|
||||
|
||||
# `Transaction`
|
||||
|
||||
## Constructor
|
||||
|
||||
You can create a transaction manually, all arguments are optional, but with empty `vin` and `vout` raw transaction doesn't make sense. You can mutate `tx.vin` and `tx.vout` after creation though.
|
||||
|
||||
```py
|
||||
Transaction(version=2, vin=[], vout=[], locktime=0)
|
||||
```
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `version = 2` - transaction version. Version 2 adds support for relative timelocks as defined in [BIP-68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki)
|
||||
- `vin` - transaction inputs, list of `TransactionInput` class instances.
|
||||
- `vout` - transaction outputs, list of `TransactionOutput` class instances.
|
||||
- `locktime = 0` - minimal block height when transaction can be mined, a good practice to set it to the current block height.
|
||||
|
||||
## Serialization
|
||||
|
||||
Parsing:
|
||||
|
||||
- `Transaction.parse(bytes)` - parses transaction from byte array, returns an instance of the `Transaction`
|
||||
- `Transaction.read_from(stream)` - parses transaction from byte stream like file or `BytesIO` object, returns an instance of the `Transaction`
|
||||
- `Transaction.read_vout(stream, idx)` - a memory-efficient parsing of the transaction if you only need a particular `TransactionOutput`. `idx` is the index of the output you are interested in. Returns a tuple: instance of `TransactionOutput` that was found in the transaction at index `idx`, tx hash without witness (`32` bytes, reverse of the txid).
|
||||
|
||||
## Attributes
|
||||
|
||||
You can change them at any time.
|
||||
|
||||
!> Note that signatures commit to version and locktime, so changing version number or locktime in a signed transaction will make the transaction invalid.
|
||||
|
||||
- `version` - transaction version
|
||||
- `vin` - list of inputs
|
||||
- `vout` - list of outputs
|
||||
- `locktime` - minimal block height when transaction can be mined
|
||||
|
||||
## Properties
|
||||
|
||||
These properties only implement getter, so you can't change them.
|
||||
|
||||
- `is_segwit` - returns `True` if any input of the transaction contains non-empty witness. Note that unsigned segwit transaction will return `False` until you fill in witness data.
|
||||
|
||||
## Methods
|
||||
|
||||
- `txid()` - returns txid of the transaction (`32` bytes), reversed of the `tx.hash()`
|
||||
- `hash()` - returns a hash of the transaction without witness using double-sha256
|
||||
- [`sighash_legacy(input_index, script_pubkey, sighash=SIGHASH.ALL)`](#sighash_legacy) - returns a `32` byte hash to sign for legacy input
|
||||
- [`sighash_segwit(input_index, script_pubkey, value, sighash=SIGHASH.ALL)`](#sighash_segwit) - returns a `32` byte hash to sign for segwit input
|
||||
- [`sighash_taproot(input_index, script_pubkeys, values, sighash=SIGHASH.DEFAULT)`](#sighash_taproot) - returns a `32` byte hash to sign for taproot input
|
||||
- `clear_cache()` - removes segwit signing cache
|
||||
|
||||
### `sighash_legacy()`
|
||||
|
||||
Legacy sighash only needs information about the scriptpubkey of the output it is spending.
|
||||
|
||||
**Arguments**
|
||||
|
||||
- `input_index: int` - index of the input that you want to sign
|
||||
- `script_pubkey: script.Script` - script pubkey of the output this input is spending
|
||||
- `sighash = SIGHASH.ALL` - what sighash to use, for more info see [`SIGHASH`](#SIGHASH) section
|
||||
|
||||
**Returns**
|
||||
|
||||
a `32` byte hash to sign
|
||||
|
||||
**Example**
|
||||
|
||||
This example assumes a single-address legacy wallet with p2pkh script type.
|
||||
|
||||
```python
|
||||
from embit.transaction import Transaction, SIGHASH
|
||||
from embit.ec import PrivateKey
|
||||
from embit.script import p2pkh, Script
|
||||
|
||||
# private key that we will use to sign input
|
||||
pk = ec.PrivateKey.from_string("L4WK9uFXiVRUopgcs41DmThuJdrmnNPX43E9TNhyjsb6Vh2eJcuB")
|
||||
|
||||
# read legacy transaction we want to sign from a file
|
||||
with open("legacy.tx", "rb") as f
|
||||
tx = Transaction.read_from(f)
|
||||
|
||||
# calculate hashes for all inputs, sign them with our private key and put to scriptsig
|
||||
# a valid scriptsig should contain a signature with sighash byte and a pubkey
|
||||
for i, inp in enumerate(tx.vin):
|
||||
# get hash to sign using p2pkh script type
|
||||
msg = tx.sighash_legacy(i, p2pkh(pk))
|
||||
sig = pk.sign(msg)
|
||||
# create a valid legacy scriptsig
|
||||
sc = Script()
|
||||
# append signature sighash
|
||||
sc.push(sig.serialize() + bytes([SIGHASH.ALL]))
|
||||
sc.push(pk.sec())
|
||||
# set scriptsig
|
||||
inp.script_sig = sc
|
||||
|
||||
# write signed transaction to file
|
||||
with open("legacy_signed.tx", "wb") as f:
|
||||
tx.write_to(f)
|
||||
```
|
||||
|
||||
### `sighash_segwit()`
|
||||
|
||||
Segwit sighash needs information about the scriptpubkey and the amount of the output it is spending.
|
||||
|
||||
**Arguments**
|
||||
|
||||
- `input_index: int` - index of the input that you want to sign
|
||||
- `script_pubkey: script.Script` - script pubkey of the output this input is spending
|
||||
- `value: int` - value of the output this input is spending in satoshi
|
||||
- `sighash = SIGHASH.ALL` - what sighash to use, for more info see [`SIGHASH`](#SIGHASH) section
|
||||
|
||||
**Returns**
|
||||
|
||||
a `32` byte hash to sign
|
||||
|
||||
**Example**
|
||||
|
||||
This example assumes a single-address native segwit wallet with p2wpkh script type.
|
||||
|
||||
Also assuming that the transaction has one input that is spending `1000000` sats.
|
||||
|
||||
!> Note that even though we are signing `p2wpkh` input we still use `p2pkh` script type!
|
||||
|
||||
```python
|
||||
from embit.transaction import Transaction, SIGHASH
|
||||
from embit.ec import PrivateKey
|
||||
from embit.script import p2pkh, Witness
|
||||
|
||||
# private key that we will use to sign input
|
||||
pk = ec.PrivateKey.from_string("L4WK9uFXiVRUopgcs41DmThuJdrmnNPX43E9TNhyjsb6Vh2eJcuB")
|
||||
|
||||
# read legacy transaction we want to sign from a file
|
||||
with open("segwit.tx", "rb") as f
|
||||
tx = Transaction.read_from(f)
|
||||
|
||||
# get hash to sign using p2pkh script type
|
||||
# p2wpkh is a special case and it uses p2pkh in the sighash!
|
||||
msg = tx.sighash_segwit(0, p2pkh(pk), 1000_000)
|
||||
sig = pk.sign(msg)
|
||||
|
||||
# create a witness with two items - signature with sighash flag, and pubkey
|
||||
w = Witness([
|
||||
sig.serialize() + bytes([SIGHASH.ALL]),
|
||||
pk.sec(),
|
||||
])
|
||||
# set witness
|
||||
tx.vin[0].witness = sc
|
||||
|
||||
# if you are using nested segwit you also need to set scriptsig to your witness script:
|
||||
# tx.vin[0].script_sig = p2wpkh(pk)
|
||||
|
||||
# write signed transaction to file
|
||||
with open("segwit_signed.tx", "wb") as f:
|
||||
tx.write_to(f)
|
||||
```
|
||||
|
||||
### `sighash_taproot()`
|
||||
|
||||
Taproot sighash needs information about ALL scriptpubkeys and amounts of the outputs this transaction is spending.
|
||||
|
||||
**Arguments**
|
||||
|
||||
- `input_index: int` - index of the input that you want to sign
|
||||
- `script_pubkeys: list(script.Script)` - list of all script pubkeys of the outputs this transaction is spending
|
||||
- `values: list(int)` - list of all values of the outputs this transaction is spending in satoshi
|
||||
- `sighash = SIGHASH.DEFAULT` - what sighash to use, for more info see [`SIGHASH`](#SIGHASH) section
|
||||
|
||||
**Returns**
|
||||
|
||||
a `32` byte hash to sign using `schnorr_sign` function.
|
||||
|
||||
**Example**
|
||||
|
||||
This example assumes a single-address taproot wallet with key-only p2tr script type.
|
||||
|
||||
Also assuming that the transaction has one input that is spending `1000000` sats.
|
||||
|
||||
```python
|
||||
from embit.transaction import Transaction, SIGHASH
|
||||
from embit.ec import PrivateKey
|
||||
from embit.script import p2tr, Witness
|
||||
|
||||
# private key that we will use to sign input
|
||||
pk = ec.PrivateKey.from_string("L4WK9uFXiVRUopgcs41DmThuJdrmnNPX43E9TNhyjsb6Vh2eJcuB")
|
||||
# tweaked taproot key with empty tapscript
|
||||
tweaked = pk.taproot_tweak()
|
||||
|
||||
# read legacy transaction we want to sign from a file
|
||||
with open("taproot.tx", "rb") as f
|
||||
tx = Transaction.read_from(f)
|
||||
|
||||
msg = tx.sighash_taproot(0, [p2tr(pk)], [1000_000])
|
||||
# use tweaked private key to sign the message
|
||||
sig = tweaked.schnorr_sign(msg)
|
||||
|
||||
# create a witness the signature
|
||||
# we don't need to add sighash because we use SIGHASH.DEFAULT
|
||||
# and we don't need pubkey because p2tr already has taproot
|
||||
w = Witness([
|
||||
sig.serialize()
|
||||
])
|
||||
# set witness
|
||||
tx.vin[0].witness = sc
|
||||
|
||||
# write signed transaction to file
|
||||
with open("taproot_signed.tx", "wb") as f:
|
||||
tx.write_to(f)
|
||||
```
|
||||
|
||||
# `TransactionInput`
|
||||
|
||||
## Constructor
|
||||
|
||||
```py
|
||||
TransactionInput(txid, vout, script_sig=None, sequence=0xFFFFFFFF, witness=None)
|
||||
```
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `txid: bytes` - `32` bytes, txid of the transaction this input is spending.
|
||||
- `vout: int` - index of the output this input is spending.
|
||||
- `script_sig: script.Script` - input script sig, if `None` (default) - it will create an empty script sig.
|
||||
- `sequence = 0xFFFFFFFF` - sequence number. For Replace-by-fee set it to lower value for example `0xfffffffe`. Also used by timelocked transaction, see [BIP-68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki).
|
||||
- `witness: script.Witness` - input witness, if `None` (default) - set to empty witness.
|
||||
|
||||
Serialization and parsing is done as usual - `from_string(hex)`, `to_string()`, `parse(bytes)`, `serialize()`, `read_from(stream)`, `write_to(stream)`.
|
||||
|
||||
## Attributes
|
||||
|
||||
You can change them at any time.
|
||||
|
||||
- `txid: bytes` - `32` bytes, txid of the transaction this input is spending.
|
||||
- `vout: int` - index of the output this input is spending.
|
||||
- `script_sig: script.Script` - input script sig, if `None` (default) - it will create an empty script sig.
|
||||
- `sequence: int` - sequence number. Used for Replace-by-fee signalling and in timelocked transaction, see [BIP-68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki).
|
||||
- `witness: script.Witness` - input witness. Legacy and unsigned inputs have empty witness.
|
||||
|
||||
## Properties
|
||||
|
||||
These properties only implement getter, so you can't change them.
|
||||
|
||||
- `is_segwit` - returns `True` if the input has non-empty witness. Note that unsigned segwit transaction input will return `False` until you fill in witness data.
|
||||
|
||||
|
||||
# `TransactionOutput`
|
||||
|
||||
## Constructor
|
||||
|
||||
```py
|
||||
TransactionOutput(value, script_pubkey)
|
||||
```
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- `value: int` - value of the output in satoshi
|
||||
- `script_pubkey: script.Script` - output script defining spending conditions.
|
||||
|
||||
Serialization and parsing is done as usual - `from_string(hex)`, `to_string()`, `parse(bytes)`, `serialize()`, `read_from(stream)`, `write_to(stream)`.
|
||||
|
||||
## Attributes
|
||||
|
||||
You can change them at any time.
|
||||
|
||||
- `value: int` - value of the output in satoshi
|
||||
- `script_pubkey: script.Script` - output script defining spending conditions.
|
||||
|
||||
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_namespace_packages
|
||||
|
||||
setup(
|
||||
name="embit",
|
||||
version="0.4.12",
|
||||
version="0.4.13",
|
||||
license="MIT license",
|
||||
url="https://github.com/diybitcoinhardware/embit",
|
||||
description="yet another bitcoin library",
|
||||
|
||||
@ -227,10 +227,10 @@ class PrivateKey(EmbitKey):
|
||||
return SchnorrSig(secp256k1.schnorrsig_sign(msg_hash, self._secret))
|
||||
|
||||
def verify(self, sig, msg_hash):
|
||||
self.get_public_key().verify(sig, msg_hash)
|
||||
return self.get_public_key().verify(sig, msg_hash)
|
||||
|
||||
def schnorr_verify(self, sig, msg_hash) -> bool:
|
||||
self.get_public_key().schnorr_verify(sig, msg_hash)
|
||||
return self.get_public_key().schnorr_verify(sig, msg_hash)
|
||||
|
||||
def write_to(self, stream) -> int:
|
||||
# return a copy of the secret
|
||||
|
||||
@ -1,6 +1,14 @@
|
||||
import sys
|
||||
import hashlib
|
||||
|
||||
try:
|
||||
# this will work with micropython and python < 3.10
|
||||
# but will raise and exception if ripemd is not supported (python3.10, openssl 3)
|
||||
hashlib.new('ripemd160')
|
||||
def ripemd160(msg):
|
||||
return hashlib.new('ripemd160', msg).digest()
|
||||
except:
|
||||
# otherwise use pure python implementation
|
||||
from .util.py_ripemd160 import ripemd160
|
||||
|
||||
def double_sha256(msg):
|
||||
"""sha256(sha256(msg)) -> bytes"""
|
||||
@ -9,7 +17,7 @@ def double_sha256(msg):
|
||||
|
||||
def hash160(msg):
|
||||
"""ripemd160(sha256(msg)) -> bytes"""
|
||||
return hashlib.new('ripemd160', hashlib.sha256(msg).digest()).digest()
|
||||
return ripemd160(hashlib.sha256(msg).digest())
|
||||
|
||||
|
||||
def sha256(msg):
|
||||
@ -17,11 +25,6 @@ def sha256(msg):
|
||||
return hashlib.sha256(msg).digest()
|
||||
|
||||
|
||||
def ripemd160(msg):
|
||||
"""one-line rmd160(msg) -> bytes"""
|
||||
return hashlib.new('ripemd160', msg).digest()
|
||||
|
||||
|
||||
def tagged_hash(tag: str, data: bytes) -> bytes:
|
||||
"""BIP-Schnorr tag-specific key derivation"""
|
||||
hashtag = hashlib.sha256(tag.encode()).digest()
|
||||
|
||||
@ -42,6 +42,9 @@ class Script(EmbitBase):
|
||||
# we should never get here
|
||||
raise ValueError("Unsupported script type")
|
||||
|
||||
def push(self, data):
|
||||
self.data += compact.to_bytes(len(data)) + data
|
||||
|
||||
def script_type(self):
|
||||
data = self.data
|
||||
# OP_DUP OP_HASH160 <20:hash160(pubkey)> OP_EQUALVERIFY OP_CHECKSIG
|
||||
@ -75,6 +78,13 @@ class Script(EmbitBase):
|
||||
raise ValueError("Cant read %d bytes" % l)
|
||||
return cls(data)
|
||||
|
||||
@classmethod
|
||||
def from_address(cls, addr:str):
|
||||
"""
|
||||
Decodes a bitcoin address and returns corresponding scriptpubkey.
|
||||
"""
|
||||
return address_to_scriptpubkey(addr)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.data == other.data
|
||||
|
||||
|
||||
107
src/embit/util/py_ripemd160.py
Normal file
107
src/embit/util/py_ripemd160.py
Normal file
@ -0,0 +1,107 @@
|
||||
# Copyright (c) 2021 Pieter Wuille
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Pure Python RIPEMD160 implementation."""
|
||||
|
||||
# Message schedule indexes for the left path.
|
||||
ML = [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,
|
||||
3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,
|
||||
1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,
|
||||
4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13
|
||||
]
|
||||
|
||||
# Message schedule indexes for the right path.
|
||||
MR = [
|
||||
5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,
|
||||
6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,
|
||||
15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,
|
||||
8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,
|
||||
12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11
|
||||
]
|
||||
|
||||
# Rotation counts for the left path.
|
||||
RL = [
|
||||
11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,
|
||||
7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,
|
||||
11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,
|
||||
11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,
|
||||
9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6
|
||||
]
|
||||
|
||||
# Rotation counts for the right path.
|
||||
RR = [
|
||||
8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,
|
||||
9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,
|
||||
9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,
|
||||
15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,
|
||||
8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11
|
||||
]
|
||||
|
||||
# K constants for the left path.
|
||||
KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e]
|
||||
|
||||
# K constants for the right path.
|
||||
KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0]
|
||||
|
||||
|
||||
def fi(x, y, z, i):
|
||||
"""The f1, f2, f3, f4, and f5 functions from the specification."""
|
||||
if i == 0:
|
||||
return x ^ y ^ z
|
||||
elif i == 1:
|
||||
return (x & y) | (~x & z)
|
||||
elif i == 2:
|
||||
return (x | ~y) ^ z
|
||||
elif i == 3:
|
||||
return (x & z) | (y & ~z)
|
||||
elif i == 4:
|
||||
return x ^ (y | ~z)
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
||||
def rol(x, i):
|
||||
"""Rotate the bottom 32 bits of x left by i bits."""
|
||||
return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff
|
||||
|
||||
|
||||
def compress(h0, h1, h2, h3, h4, block):
|
||||
"""Compress state (h0, h1, h2, h3, h4) with block."""
|
||||
# Left path variables.
|
||||
al, bl, cl, dl, el = h0, h1, h2, h3, h4
|
||||
# Right path variables.
|
||||
ar, br, cr, dr, er = h0, h1, h2, h3, h4
|
||||
# Message variables.
|
||||
x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)]
|
||||
|
||||
# Iterate over the 80 rounds of the compression.
|
||||
for j in range(80):
|
||||
rnd = j >> 4
|
||||
# Perform left side of the transformation.
|
||||
al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el
|
||||
al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl
|
||||
# Perform right side of the transformation.
|
||||
ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er
|
||||
ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr
|
||||
|
||||
# Compose old state, left transform, and right transform into new state.
|
||||
return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr
|
||||
|
||||
|
||||
def ripemd160(data):
|
||||
"""Compute the RIPEMD-160 hash of data."""
|
||||
# Initialize state.
|
||||
state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0)
|
||||
# Process full 64-byte blocks in the input.
|
||||
for b in range(len(data) >> 6):
|
||||
state = compress(*state, data[64*b:64*(b+1)])
|
||||
# Construct final blocks (with padding and size).
|
||||
pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63)
|
||||
fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little')
|
||||
# Process final blocks.
|
||||
for b in range(len(fin) >> 6):
|
||||
state = compress(*state, fin[64*b:64*(b+1)])
|
||||
# Produce output.
|
||||
return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state)
|
||||
@ -11,6 +11,8 @@ from .test_liquid import *
|
||||
from .test_psbtview import *
|
||||
from .test_psetview import *
|
||||
from .test_taproot import *
|
||||
from .test_script import *
|
||||
from .test_ripemd160 import *
|
||||
|
||||
if sys.implementation.name != "micropython":
|
||||
from .test_bindings import *
|
||||
|
||||
31
tests/tests/test_ripemd160.py
Normal file
31
tests/tests/test_ripemd160.py
Normal file
@ -0,0 +1,31 @@
|
||||
from unittest import TestCase
|
||||
from embit.hashes import ripemd160
|
||||
from embit.util.py_ripemd160 import ripemd160 as py_ripemd160
|
||||
import os
|
||||
|
||||
class Ripemd160Test(TestCase):
|
||||
def test_vs_hashlib(self):
|
||||
# tests python version against hashlib version
|
||||
for l in range(1,60):
|
||||
data = os.urandom(l)
|
||||
self.assertEqual(ripemd160(data), py_ripemd160(data))
|
||||
|
||||
def test_ripemd160(self):
|
||||
"""RIPEMD-160 test vectors."""
|
||||
# See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
|
||||
for msg, hexout in [
|
||||
(b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"),
|
||||
(b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"),
|
||||
(b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"),
|
||||
(b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"),
|
||||
(b"abcdefghijklmnopqrstuvwxyz",
|
||||
"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"),
|
||||
(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
|
||||
"12a053384a9c0c88e405a06c27dcf49ada62eb2b"),
|
||||
(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
||||
"b0e20b6e3116640286ed3a87a5713079b21f5189"),
|
||||
(b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"),
|
||||
(b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528")
|
||||
]:
|
||||
self.assertEqual(ripemd160(msg).hex(), hexout)
|
||||
self.assertEqual(py_ripemd160(msg).hex(), hexout)
|
||||
22
tests/tests/test_script.py
Normal file
22
tests/tests/test_script.py
Normal file
@ -0,0 +1,22 @@
|
||||
from unittest import TestCase
|
||||
from embit.script import Script, p2wpkh, p2sh, p2pkh, p2tr
|
||||
from embit.ec import PrivateKey
|
||||
from embit.hashes import hash160
|
||||
|
||||
class ScriptTest(TestCase):
|
||||
def test_from_addr(self):
|
||||
pk = PrivateKey(b"\x11"*32)
|
||||
scripts = [
|
||||
p2wpkh(pk),
|
||||
p2pkh(pk),
|
||||
p2sh(p2wpkh(pk)),
|
||||
p2tr(pk),
|
||||
]
|
||||
for sc in scripts:
|
||||
self.assertEqual(sc, Script.from_address(sc.address()))
|
||||
|
||||
def test_push(self):
|
||||
pk = PrivateKey(b"\x11"*32)
|
||||
sc = Script(b"\x00")
|
||||
sc.push(hash160(pk.sec()))
|
||||
self.assertEqual(sc, p2wpkh(pk))
|
||||
Loading…
Reference in New Issue
Block a user