mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-23 03:34:19 +01:00
use python-bitcoinlib instead of python-bitcointx
This commit is contained in:
@@ -8,6 +8,7 @@ class Proof(BaseModel):
|
|||||||
amount: int
|
amount: int
|
||||||
secret: str = ""
|
secret: str = ""
|
||||||
C: str
|
C: str
|
||||||
|
script: str = ""
|
||||||
reserved: bool = False # whether this proof is reserved for sending
|
reserved: bool = False # whether this proof is reserved for sending
|
||||||
send_id: str = "" # unique ID of send attempt
|
send_id: str = "" # unique ID of send attempt
|
||||||
time_created: str = ""
|
time_created: str = ""
|
||||||
|
|||||||
@@ -86,6 +86,24 @@ class Ledger:
|
|||||||
C = PublicKey(bytes.fromhex(proof.C), raw=True)
|
C = PublicKey(bytes.fromhex(proof.C), raw=True)
|
||||||
return b_dhke.verify(secret_key, C, proof.secret)
|
return b_dhke.verify(secret_key, C, proof.secret)
|
||||||
|
|
||||||
|
def _verify_script(self, proof: Proof):
|
||||||
|
print(f"secret: {proof.secret}")
|
||||||
|
print(f"script: {proof.script}")
|
||||||
|
print(
|
||||||
|
f"script_hash: {hashlib.sha256(proof.script.encode('utf-8')).hexdigest()}"
|
||||||
|
)
|
||||||
|
if len(proof.secret.split("SCRIPT:")) != 2:
|
||||||
|
return True
|
||||||
|
if len(proof.script) < 16:
|
||||||
|
raise Exception("Script error: not long enough.")
|
||||||
|
if (
|
||||||
|
hashlib.sha256(proof.script.encode("utf-8")).hexdigest()
|
||||||
|
!= proof.secret.split("SCRIPT:")[1]
|
||||||
|
):
|
||||||
|
raise Exception("Script error: script hash not valid.")
|
||||||
|
print(f"Script {proof.script} valid.")
|
||||||
|
return True
|
||||||
|
|
||||||
def _verify_outputs(
|
def _verify_outputs(
|
||||||
self, total: int, amount: int, output_data: List[BlindedMessage]
|
self, total: int, amount: int, output_data: List[BlindedMessage]
|
||||||
):
|
):
|
||||||
@@ -242,7 +260,7 @@ class Ledger:
|
|||||||
# verify overspending attempt
|
# verify overspending attempt
|
||||||
if amount > total:
|
if amount > total:
|
||||||
raise Exception("split amount is higher than the total sum.")
|
raise Exception("split amount is higher than the total sum.")
|
||||||
# Verify proofs
|
# Verify secret criteria
|
||||||
if not all([self._verify_secret_criteria(p) for p in proofs]):
|
if not all([self._verify_secret_criteria(p) for p in proofs]):
|
||||||
raise Exception("secrets do not match criteria.")
|
raise Exception("secrets do not match criteria.")
|
||||||
# verify that only unique proofs and outputs were used
|
# verify that only unique proofs and outputs were used
|
||||||
@@ -254,6 +272,9 @@ class Ledger:
|
|||||||
# Verify proofs
|
# Verify proofs
|
||||||
if not all([self._verify_proof(p) for p in proofs]):
|
if not all([self._verify_proof(p) for p in proofs]):
|
||||||
raise Exception("could not verify proofs.")
|
raise Exception("could not verify proofs.")
|
||||||
|
# Verify scripts
|
||||||
|
if not all([self._verify_script(p) for p in proofs]):
|
||||||
|
raise Exception("could not verify scripts.")
|
||||||
|
|
||||||
# Mark proofs as used and prepare new promises
|
# Mark proofs as used and prepare new promises
|
||||||
await self._invalidate_proofs(proofs)
|
await self._invalidate_proofs(proofs)
|
||||||
|
|||||||
@@ -124,14 +124,15 @@ async def send(ctx, amount: int, secret: str):
|
|||||||
@cli.command("receive", help="Receive tokens.")
|
@cli.command("receive", help="Receive tokens.")
|
||||||
@click.argument("token", type=str)
|
@click.argument("token", type=str)
|
||||||
@click.option("--secret", "-s", default="", help="Token spending condition.", type=str)
|
@click.option("--secret", "-s", default="", help="Token spending condition.", type=str)
|
||||||
|
@click.option("--script", default=None, help="Token unlock script.", type=str)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
@coro
|
@coro
|
||||||
async def receive(ctx, token: str, secret: str):
|
async def receive(ctx, token: str, secret: str, script: str):
|
||||||
wallet: Wallet = ctx.obj["WALLET"]
|
wallet: Wallet = ctx.obj["WALLET"]
|
||||||
wallet.load_mint()
|
wallet.load_mint()
|
||||||
wallet.status()
|
wallet.status()
|
||||||
proofs = [Proof.from_dict(p) for p in json.loads(base64.urlsafe_b64decode(token))]
|
proofs = [Proof.from_dict(p) for p in json.loads(base64.urlsafe_b64decode(token))]
|
||||||
_, _ = await wallet.redeem(proofs, secret)
|
_, _ = await wallet.redeem(proofs, secret, script)
|
||||||
wallet.status()
|
wallet.status()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class LedgerAPI:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_deterministic_secrets(secret, n):
|
def generate_deterministic_secrets(secret, n):
|
||||||
"""`secret` is the base string that will be tweaked n times"""
|
"""`secret` is the base string that will be tweaked n times"""
|
||||||
return [f"{secret}_{i}" for i in range(n)]
|
return [f"{i}:{secret}" for i in range(n)]
|
||||||
|
|
||||||
async def mint(self, amounts, payment_hash=None):
|
async def mint(self, amounts, payment_hash=None):
|
||||||
"""Mints new coins and returns a proof of promise."""
|
"""Mints new coins and returns a proof of promise."""
|
||||||
@@ -132,7 +132,9 @@ class LedgerAPI:
|
|||||||
promises = [BlindedSignature.from_dict(p) for p in promises_list]
|
promises = [BlindedSignature.from_dict(p) for p in promises_list]
|
||||||
return self._construct_proofs(promises, secrets, rs)
|
return self._construct_proofs(promises, secrets, rs)
|
||||||
|
|
||||||
async def split(self, proofs, amount, snd_secret: str = None):
|
async def split(
|
||||||
|
self, proofs, amount, snd_secret: str = None, has_script: bool = False
|
||||||
|
):
|
||||||
"""Consume proofs and create new promises based on amount split.
|
"""Consume proofs and create new promises based on amount split.
|
||||||
If snd_secret is None, random secrets will be generated for the tokens to keep (fst_outputs)
|
If snd_secret is None, random secrets will be generated for the tokens to keep (fst_outputs)
|
||||||
and the promises to send (snd_outputs).
|
and the promises to send (snd_outputs).
|
||||||
@@ -147,7 +149,6 @@ class LedgerAPI:
|
|||||||
|
|
||||||
amounts = fst_outputs + snd_outputs
|
amounts = fst_outputs + snd_outputs
|
||||||
if snd_secret is None:
|
if snd_secret is None:
|
||||||
logger.debug("Generating random secrets.")
|
|
||||||
secrets = [self._generate_secret() for _ in range(len(amounts))]
|
secrets = [self._generate_secret() for _ in range(len(amounts))]
|
||||||
else:
|
else:
|
||||||
logger.debug(f"Creating proofs with custom secret: {snd_secret}")
|
logger.debug(f"Creating proofs with custom secret: {snd_secret}")
|
||||||
@@ -243,7 +244,9 @@ class Wallet(LedgerAPI):
|
|||||||
self.proofs += proofs
|
self.proofs += proofs
|
||||||
return proofs
|
return proofs
|
||||||
|
|
||||||
async def redeem(self, proofs: List[Proof], snd_secret: str = None):
|
async def redeem(
|
||||||
|
self, proofs: List[Proof], snd_secret: str = None, snd_script: str = None
|
||||||
|
):
|
||||||
if snd_secret:
|
if snd_secret:
|
||||||
logger.debug(f"Redeption secret: {snd_secret}")
|
logger.debug(f"Redeption secret: {snd_secret}")
|
||||||
snd_secrets = self.generate_deterministic_secrets(snd_secret, len(proofs))
|
snd_secrets = self.generate_deterministic_secrets(snd_secret, len(proofs))
|
||||||
@@ -251,11 +254,26 @@ class Wallet(LedgerAPI):
|
|||||||
# overload proofs with custom secrets for redemption
|
# overload proofs with custom secrets for redemption
|
||||||
for p, s in zip(proofs, snd_secrets):
|
for p, s in zip(proofs, snd_secrets):
|
||||||
p.secret = s
|
p.secret = s
|
||||||
return await self.split(proofs, sum(p["amount"] for p in proofs))
|
if snd_script:
|
||||||
|
logger.debug(f"Unlock script: {snd_script}")
|
||||||
|
# overload proofs with unlock script
|
||||||
|
for p in proofs:
|
||||||
|
p.script = snd_script
|
||||||
|
return await self.split(
|
||||||
|
proofs, sum(p["amount"] for p in proofs), has_script=snd_script is not None
|
||||||
|
)
|
||||||
|
|
||||||
async def split(self, proofs: List[Proof], amount: int, snd_secret: str = None):
|
async def split(
|
||||||
|
self,
|
||||||
|
proofs: List[Proof],
|
||||||
|
amount: int,
|
||||||
|
snd_secret: str = None,
|
||||||
|
has_script: bool = False,
|
||||||
|
):
|
||||||
assert len(proofs) > 0, ValueError("no proofs provided.")
|
assert len(proofs) > 0, ValueError("no proofs provided.")
|
||||||
fst_proofs, snd_proofs = await super().split(proofs, amount, snd_secret)
|
fst_proofs, snd_proofs = await super().split(
|
||||||
|
proofs, amount, snd_secret, has_script
|
||||||
|
)
|
||||||
if len(fst_proofs) == 0 and len(snd_proofs) == 0:
|
if len(fst_proofs) == 0 and len(snd_proofs) == 0:
|
||||||
raise Exception("received no splits.")
|
raise Exception("received no splits.")
|
||||||
used_secrets = [p["secret"] for p in proofs]
|
used_secrets = [p["secret"] for p in proofs]
|
||||||
|
|||||||
14
poetry.lock
generated
14
poetry.lock
generated
@@ -430,6 +430,14 @@ typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""}
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
|
testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-bitcoinlib"
|
||||||
|
version = "0.11.2"
|
||||||
|
description = "The Swiss Army Knife of the Bitcoin protocol."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dotenv"
|
name = "python-dotenv"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
@@ -632,7 +640,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
content-hash = "fbb8f977f71b77cf9b6514134111dc466f7fa1f70f8114e47b858d85c39199e4"
|
content-hash = "be65b895cb013a28ac6a6b2aacc131d140d6eafcd071aa021dbf2d2fbe311802"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
anyio = [
|
anyio = [
|
||||||
@@ -964,6 +972,10 @@ pytest-asyncio = [
|
|||||||
{file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"},
|
{file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"},
|
||||||
{file = "pytest_asyncio-0.19.0-py3-none-any.whl", hash = "sha256:7a97e37cfe1ed296e2e84941384bdd37c376453912d397ed39293e0916f521fa"},
|
{file = "pytest_asyncio-0.19.0-py3-none-any.whl", hash = "sha256:7a97e37cfe1ed296e2e84941384bdd37c376453912d397ed39293e0916f521fa"},
|
||||||
]
|
]
|
||||||
|
python-bitcoinlib = [
|
||||||
|
{file = "python-bitcoinlib-0.11.2.tar.gz", hash = "sha256:61ba514e0d232cc84741e49862dcedaf37199b40bba252a17edc654f63d13f39"},
|
||||||
|
{file = "python_bitcoinlib-0.11.2-py3-none-any.whl", hash = "sha256:78bd4ee717fe805cd760dfdd08765e77b7c7dbef4627f8596285e84953756508"},
|
||||||
|
]
|
||||||
python-dotenv = [
|
python-dotenv = [
|
||||||
{file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"},
|
{file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"},
|
||||||
{file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"},
|
{file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"},
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ ecdsa = "^0.18.0"
|
|||||||
bitstring = "^3.1.9"
|
bitstring = "^3.1.9"
|
||||||
secp256k1 = "^0.14.0"
|
secp256k1 = "^0.14.0"
|
||||||
sqlalchemy-aio = "^0.17.0"
|
sqlalchemy-aio = "^0.17.0"
|
||||||
|
python-bitcoinlib = "^0.11.2"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
black = {version = "^22.8.0", allow-prereleases = true}
|
black = {version = "^22.8.0", allow-prereleases = true}
|
||||||
|
|||||||
Reference in New Issue
Block a user