use python-bitcoinlib instead of python-bitcointx

This commit is contained in:
callebtc
2022-10-02 11:51:47 +02:00
parent c72ea75911
commit b5e03e4fc7
6 changed files with 65 additions and 11 deletions

View File

@@ -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 = ""

View File

@@ -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)

View File

@@ -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()

View File

@@ -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
View File

@@ -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"},

View File

@@ -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}