mirror of
https://github.com/aljazceru/nutshell.git
synced 2026-01-04 17:34:20 +01:00
SIG_ALL signature flag for P2PK (#735)
* n_sigs_refund working, tests added * update requirements * wip sigall * wip * sigall works * add signatures for refund * add mint p2pk tests * add more p2pk tests * fix tests * sign htlc pubkeys as well * fix htlc and add new test * fix regtest * fix new tests with deprecated * remove asserts * comments * new wallet p2pk tests * getting there * add more tests * fixes * refactor htlc and p2pk validation * reduce code * melt with sigall * fix htlcs * fix deprecated api tests * Update cashu/mint/conditions.py Co-authored-by: lollerfirst <43107113+lollerfirst@users.noreply.github.com> * refactor sigall validation --------- Co-authored-by: lollerfirst <43107113+lollerfirst@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import copy
|
||||
import hashlib
|
||||
import secrets
|
||||
from typing import List
|
||||
@@ -10,6 +11,8 @@ from cashu.core.base import HTLCWitness, Proof
|
||||
from cashu.core.crypto.secp import PrivateKey
|
||||
from cashu.core.htlc import HTLCSecret
|
||||
from cashu.core.migrations import migrate_databases
|
||||
from cashu.core.p2pk import SigFlags
|
||||
from cashu.core.secret import SecretKind
|
||||
from cashu.wallet import migrations
|
||||
from cashu.wallet.wallet import Wallet
|
||||
from cashu.wallet.wallet import Wallet as Wallet1
|
||||
@@ -107,7 +110,7 @@ async def test_htlc_redeem_with_wrong_preimage(wallet1: Wallet, wallet2: Wallet)
|
||||
for p in send_proofs:
|
||||
p.witness = HTLCWitness(preimage=preimage).json()
|
||||
await assert_err(
|
||||
wallet2.redeem(send_proofs), "Mint Error: HTLC preimage does not match"
|
||||
wallet1.redeem(send_proofs), "Mint Error: HTLC preimage does not match"
|
||||
)
|
||||
|
||||
|
||||
@@ -143,7 +146,7 @@ async def test_htlc_redeem_with_wrong_signature(wallet1: Wallet, wallet2: Wallet
|
||||
preimage=preimage, hashlock_pubkeys=[pubkey_wallet1]
|
||||
)
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||
signatures = wallet1.sign_proofs(send_proofs)
|
||||
signatures = wallet1.signatures_proofs_sig_inputs(send_proofs)
|
||||
for p, s in zip(send_proofs, signatures):
|
||||
p.witness = HTLCWitness(
|
||||
preimage=preimage, signatures=[f"{s[:-5]}11111"]
|
||||
@@ -151,7 +154,7 @@ async def test_htlc_redeem_with_wrong_signature(wallet1: Wallet, wallet2: Wallet
|
||||
|
||||
await assert_err(
|
||||
wallet2.redeem(send_proofs),
|
||||
"Mint Error: no valid signature provided for input.",
|
||||
"Mint Error: signature threshold not met",
|
||||
)
|
||||
|
||||
|
||||
@@ -168,11 +171,11 @@ async def test_htlc_redeem_with_correct_signature(wallet1: Wallet, wallet2: Wall
|
||||
)
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||
|
||||
signatures = wallet1.sign_proofs(send_proofs)
|
||||
signatures = wallet1.signatures_proofs_sig_inputs(send_proofs)
|
||||
for p, s in zip(send_proofs, signatures):
|
||||
p.witness = HTLCWitness(preimage=preimage, signatures=[s]).json()
|
||||
|
||||
await wallet2.redeem(send_proofs)
|
||||
await wallet1.redeem(send_proofs)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -191,8 +194,8 @@ async def test_htlc_redeem_with_2_of_1_signatures(wallet1: Wallet, wallet2: Wall
|
||||
)
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||
|
||||
signatures1 = wallet1.sign_proofs(send_proofs)
|
||||
signatures2 = wallet2.sign_proofs(send_proofs)
|
||||
signatures1 = wallet1.signatures_proofs_sig_inputs(send_proofs)
|
||||
signatures2 = wallet2.signatures_proofs_sig_inputs(send_proofs)
|
||||
for p, s1, s2 in zip(send_proofs, signatures1, signatures2):
|
||||
p.witness = HTLCWitness(preimage=preimage, signatures=[s1, s2]).json()
|
||||
|
||||
@@ -215,8 +218,8 @@ async def test_htlc_redeem_with_2_of_2_signatures(wallet1: Wallet, wallet2: Wall
|
||||
)
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||
|
||||
signatures1 = wallet1.sign_proofs(send_proofs)
|
||||
signatures2 = wallet2.sign_proofs(send_proofs)
|
||||
signatures1 = wallet1.signatures_proofs_sig_inputs(send_proofs)
|
||||
signatures2 = wallet2.signatures_proofs_sig_inputs(send_proofs)
|
||||
for p, s1, s2 in zip(send_proofs, signatures1, signatures2):
|
||||
p.witness = HTLCWitness(preimage=preimage, signatures=[s1, s2]).json()
|
||||
|
||||
@@ -241,8 +244,8 @@ async def test_htlc_redeem_with_2_of_2_signatures_with_duplicate_pubkeys(
|
||||
)
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||
|
||||
signatures1 = wallet1.sign_proofs(send_proofs)
|
||||
signatures2 = wallet2.sign_proofs(send_proofs)
|
||||
signatures1 = wallet1.signatures_proofs_sig_inputs(send_proofs)
|
||||
signatures2 = wallet2.signatures_proofs_sig_inputs(send_proofs)
|
||||
for p, s1, s2 in zip(send_proofs, signatures1, signatures2):
|
||||
p.witness = HTLCWitness(preimage=preimage, signatures=[s1, s2]).json()
|
||||
|
||||
@@ -270,14 +273,14 @@ async def test_htlc_redeem_with_3_of_3_signatures_but_only_2_provided(
|
||||
)
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||
|
||||
signatures1 = wallet1.sign_proofs(send_proofs)
|
||||
signatures2 = wallet2.sign_proofs(send_proofs)
|
||||
signatures1 = wallet1.signatures_proofs_sig_inputs(send_proofs)
|
||||
signatures2 = wallet2.signatures_proofs_sig_inputs(send_proofs)
|
||||
for p, s1, s2 in zip(send_proofs, signatures1, signatures2):
|
||||
p.witness = HTLCWitness(preimage=preimage, signatures=[s1, s2]).json()
|
||||
|
||||
await assert_err(
|
||||
wallet2.redeem(send_proofs),
|
||||
"Mint Error: not enough signatures provided: 2 < 3.",
|
||||
"Mint Error: not enough pubkeys (2) or signatures (2) present for n_sigs (3).",
|
||||
)
|
||||
|
||||
|
||||
@@ -303,8 +306,8 @@ async def test_htlc_redeem_with_2_of_3_signatures_with_2_valid_and_1_invalid_pro
|
||||
)
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||
|
||||
signatures1 = wallet1.sign_proofs(send_proofs)
|
||||
signatures2 = wallet2.sign_proofs(send_proofs)
|
||||
signatures1 = wallet1.signatures_proofs_sig_inputs(send_proofs)
|
||||
signatures2 = wallet2.signatures_proofs_sig_inputs(send_proofs)
|
||||
signatures3 = [f"{s[:-5]}11111" for s in signatures1] # wrong signature
|
||||
for p, s1, s2, s3 in zip(send_proofs, signatures1, signatures2, signatures3):
|
||||
p.witness = HTLCWitness(preimage=preimage, signatures=[s1, s2, s3]).json()
|
||||
@@ -312,39 +315,6 @@ async def test_htlc_redeem_with_2_of_3_signatures_with_2_valid_and_1_invalid_pro
|
||||
await wallet2.redeem(send_proofs)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_htlc_redeem_with_3_of_3_signatures_with_2_valid_and_1_invalid_provided(
|
||||
wallet1: Wallet, wallet2: Wallet
|
||||
):
|
||||
mint_quote = await wallet1.request_mint(64)
|
||||
await pay_if_regtest(mint_quote.request)
|
||||
await wallet1.mint(64, quote_id=mint_quote.quote)
|
||||
preimage = "00000000000000000000000000000000"
|
||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||
privatekey_wallet3 = PrivateKey(secrets.token_bytes(32), raw=True)
|
||||
assert privatekey_wallet3.pubkey
|
||||
pubkey_wallet3 = privatekey_wallet3.pubkey.serialize().hex()
|
||||
|
||||
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||
secret = await wallet1.create_htlc_lock(
|
||||
preimage=preimage,
|
||||
hashlock_pubkeys=[pubkey_wallet1, pubkey_wallet2, pubkey_wallet3],
|
||||
hashlock_n_sigs=3,
|
||||
)
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||
|
||||
signatures1 = wallet1.sign_proofs(send_proofs)
|
||||
signatures2 = wallet2.sign_proofs(send_proofs)
|
||||
signatures3 = [f"{s[:-5]}11111" for s in signatures1] # wrong signature
|
||||
for p, s1, s2, s3 in zip(send_proofs, signatures1, signatures2, signatures3):
|
||||
p.witness = HTLCWitness(preimage=preimage, signatures=[s1, s2, s3]).json()
|
||||
|
||||
await assert_err(
|
||||
wallet2.redeem(send_proofs), "Mint Error: signature threshold not met. 2 < 3."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_htlc_redeem_hashlock_wrong_signature_timelock_correct_signature(
|
||||
wallet1: Wallet, wallet2: Wallet
|
||||
@@ -364,14 +334,14 @@ async def test_htlc_redeem_hashlock_wrong_signature_timelock_correct_signature(
|
||||
)
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||
|
||||
signatures = wallet1.sign_proofs(send_proofs)
|
||||
signatures = wallet1.signatures_proofs_sig_inputs(send_proofs)
|
||||
for p, s in zip(send_proofs, signatures):
|
||||
p.witness = HTLCWitness(preimage=preimage, signatures=[s]).json()
|
||||
|
||||
# should error because we used wallet2 signatures for the hash lock
|
||||
await assert_err(
|
||||
wallet1.redeem(send_proofs),
|
||||
"Mint Error: no valid signature provided for input.",
|
||||
"Mint Error: signature threshold not met",
|
||||
)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
@@ -394,11 +364,12 @@ async def test_htlc_redeem_hashlock_wrong_signature_timelock_wrong_signature(
|
||||
preimage=preimage,
|
||||
hashlock_pubkeys=[pubkey_wallet2],
|
||||
locktime_seconds=2,
|
||||
locktime_pubkeys=[pubkey_wallet1],
|
||||
locktime_pubkeys=[pubkey_wallet1, pubkey_wallet2],
|
||||
locktime_n_sigs=2,
|
||||
)
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||
|
||||
signatures = wallet1.sign_proofs(send_proofs)
|
||||
signatures = wallet1.signatures_proofs_sig_inputs(send_proofs)
|
||||
for p, s in zip(send_proofs, signatures):
|
||||
p.witness = HTLCWitness(
|
||||
preimage=preimage, signatures=[f"{s[:-5]}11111"]
|
||||
@@ -407,12 +378,175 @@ async def test_htlc_redeem_hashlock_wrong_signature_timelock_wrong_signature(
|
||||
# should error because we used wallet2 signatures for the hash lock
|
||||
await assert_err(
|
||||
wallet1.redeem(send_proofs),
|
||||
"Mint Error: no valid signature provided for input.",
|
||||
"Mint Error: signature threshold not met. 0 < 1.",
|
||||
)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
# should fail since lock time has passed and we provided a wrong signature for timelock
|
||||
# should fail since lock time has passed and we provided not enough signatures for the timelock locktime_n_sigs
|
||||
await assert_err(
|
||||
wallet1.redeem(send_proofs),
|
||||
"Mint Error: no valid signature provided for input.",
|
||||
"Mint Error: signature threshold not met. 1 < 2.",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_htlc_redeem_timelock_2_of_2_signatures(wallet1: Wallet, wallet2: Wallet):
|
||||
"""Testing the 2-of-2 timelock (refund) signature case."""
|
||||
mint_quote = await wallet1.request_mint(64)
|
||||
await pay_if_regtest(mint_quote.request)
|
||||
await wallet1.mint(64, quote_id=mint_quote.quote)
|
||||
preimage = "00000000000000000000000000000000"
|
||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||
secret = await wallet1.create_htlc_lock(
|
||||
preimage=preimage,
|
||||
hashlock_pubkeys=[pubkey_wallet2],
|
||||
locktime_seconds=2,
|
||||
locktime_pubkeys=[pubkey_wallet1, pubkey_wallet2],
|
||||
locktime_n_sigs=2,
|
||||
)
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||
send_proofs_copy = send_proofs.copy()
|
||||
|
||||
signatures = wallet1.signatures_proofs_sig_inputs(send_proofs)
|
||||
for p, s in zip(send_proofs, signatures):
|
||||
p.witness = HTLCWitness(preimage=preimage, signatures=[s]).json()
|
||||
|
||||
# should error because we used wallet2 signatures for the hash lock
|
||||
await assert_err(
|
||||
wallet1.redeem(send_proofs),
|
||||
"Mint Error: signature threshold not met. 0 < 1.",
|
||||
)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
# locktime has passed
|
||||
|
||||
# should fail. lock time has passed but we provided only wallet1 signature for timelock, we need 2 though
|
||||
await assert_err(
|
||||
wallet1.redeem(send_proofs),
|
||||
"Mint Error: not enough pubkeys (2) or signatures (1) present for n_sigs (2).",
|
||||
)
|
||||
|
||||
# let's add the second signature
|
||||
send_proofs_copy = wallet2.sign_p2pk_sig_inputs(send_proofs_copy)
|
||||
|
||||
# now we can redeem it
|
||||
await wallet1.redeem(send_proofs_copy)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_htlc_sigall_behavior(wallet1: Wallet, wallet2: Wallet):
|
||||
"""Test HTLC with SIG_ALL flag, requiring signatures on both inputs and outputs."""
|
||||
# Mint tokens for testing
|
||||
mint_quote = await wallet1.request_mint(64)
|
||||
await pay_if_regtest(mint_quote.request)
|
||||
await wallet1.mint(64, quote_id=mint_quote.quote)
|
||||
|
||||
# Setup HTLC parameters
|
||||
preimage = "00000000000000000000000000000000"
|
||||
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||
|
||||
# Create HTLC lock with SIG_ALL flag
|
||||
secret = await wallet1.create_htlc_lock(
|
||||
preimage=preimage,
|
||||
hashlock_pubkeys=[pubkey_wallet2],
|
||||
hashlock_n_sigs=1,
|
||||
)
|
||||
|
||||
# Modify the secret to use SIG_ALL
|
||||
secret.tags["sigflag"] = SigFlags.SIG_ALL.value
|
||||
|
||||
# Send tokens with this HTLC lock
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||
|
||||
# verify sigflag is SIG_ALL
|
||||
assert HTLCSecret.from_secret(secret).kind == SecretKind.HTLC.value
|
||||
assert HTLCSecret.from_secret(secret).sigflag == SigFlags.SIG_ALL
|
||||
|
||||
# first redeem fails because no preimage
|
||||
await assert_err(
|
||||
wallet2.redeem(send_proofs), "Mint Error: no HTLC preimage provided"
|
||||
)
|
||||
|
||||
# we add the preimage to the proof
|
||||
for p in send_proofs:
|
||||
p.witness = HTLCWitness(preimage=preimage).json()
|
||||
|
||||
# Should succeed, redeem adds signatures to the proof
|
||||
await wallet2.redeem(send_proofs)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_htlc_n_sigs_refund_locktime(wallet1: Wallet, wallet2: Wallet):
|
||||
"""Test HTLC with n_sigs_refund parameter requiring multiple signatures for refund after locktime."""
|
||||
# Create a third wallet for the third signature
|
||||
wallet3 = await Wallet.with_db(
|
||||
SERVER_ENDPOINT, "test_data/wallet_htlc_3", "wallet3"
|
||||
)
|
||||
await migrate_databases(wallet3.db, migrations)
|
||||
wallet3.private_key = PrivateKey(secrets.token_bytes(32), raw=True)
|
||||
await wallet3.load_mint()
|
||||
|
||||
# Mint tokens for testing
|
||||
mint_quote = await wallet1.request_mint(64)
|
||||
await pay_if_regtest(mint_quote.request)
|
||||
await wallet1.mint(64, quote_id=mint_quote.quote)
|
||||
|
||||
# Setup parameters
|
||||
preimage = "00000000000000000000000000000000"
|
||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||
pubkey_wallet3 = await wallet3.create_p2pk_pubkey()
|
||||
|
||||
# Wrong preimage - making it so we can only spend via locktime
|
||||
wrong_preimage = "11111111111111111111111111111111"
|
||||
|
||||
# Create HTLC with:
|
||||
# 1. Timelock in the past
|
||||
# 2. Three refund pubkeys with 2-of-3 signature requirement
|
||||
secret = await wallet1.create_htlc_lock(
|
||||
preimage=wrong_preimage, # this ensures we can't redeem via preimage
|
||||
hashlock_pubkeys=[pubkey_wallet2],
|
||||
locktime_seconds=-200000,
|
||||
locktime_pubkeys=[pubkey_wallet1, pubkey_wallet2, pubkey_wallet3],
|
||||
locktime_n_sigs=2, # require 2 of 3 signatures for refund
|
||||
)
|
||||
|
||||
# # Send tokens with this lock
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, 8, secret_lock=secret)
|
||||
send_proofs_copy = copy.deepcopy(send_proofs)
|
||||
|
||||
# # First, try correct preimage but should fail as we're using wrong preimage hash
|
||||
# for p in send_proofs:
|
||||
# p.witness = HTLCWitness(preimage=preimage).json()
|
||||
|
||||
# await assert_err(
|
||||
# wallet2.redeem(send_proofs), "Mint Error: HTLC preimage does not match"
|
||||
# )
|
||||
|
||||
# # Wait for locktime to pass
|
||||
# await asyncio.sleep(2)
|
||||
|
||||
# Try redeeming with only 1 signature after locktime
|
||||
signatures1 = wallet1.signatures_proofs_sig_inputs(send_proofs_copy)
|
||||
for p, sig in zip(send_proofs_copy, signatures1):
|
||||
p.witness = HTLCWitness(preimage=preimage, signatures=[sig]).json()
|
||||
|
||||
# Should fail because we need 2 signatures
|
||||
await assert_err(
|
||||
wallet1.redeem(send_proofs_copy),
|
||||
"Mint Error: not enough pubkeys (3) or signatures (1) present for n_sigs (2)",
|
||||
)
|
||||
|
||||
# Make a fresh copy and add 2 signatures
|
||||
send_proofs_copy2 = copy.deepcopy(send_proofs)
|
||||
signatures1 = wallet1.signatures_proofs_sig_inputs(send_proofs_copy2)
|
||||
signatures2 = wallet2.signatures_proofs_sig_inputs(send_proofs_copy2)
|
||||
|
||||
for p, sig1, sig2 in zip(send_proofs_copy2, signatures1, signatures2):
|
||||
p.witness = HTLCWitness(preimage=preimage, signatures=[sig1, sig2]).json()
|
||||
|
||||
# Should succeed with 2 of 3 signatures after locktime
|
||||
await wallet1.redeem(send_proofs_copy2)
|
||||
|
||||
Reference in New Issue
Block a user