Skip to content

Commit

Permalink
[CHIA-1722] Add a version of a passkey puzzle that only asserts puzzl…
Browse files Browse the repository at this point in the history
…e hash (#18886)

Mostly a duplication of the passkey member puzzle but with
`ASSERT_MY_PUZZLE_HASH` instead.
  • Loading branch information
Quexington authored Nov 19, 2024
2 parents c62edf2 + 54455c6 commit 4652380
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 4 deletions.
77 changes: 77 additions & 0 deletions chia/_tests/clvm/test_member_puzzles.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
BLSMember,
FixedPuzzleMember,
PasskeyMember,
PasskeyPuzzleAssertMember,
SECPK1Member,
SECPK1PuzzleAssertMember,
SECPR1Member,
Expand Down Expand Up @@ -298,6 +299,82 @@ async def test_passkey_member(cost_logger: CostLogger) -> None:
await sim.rewind(block_height)


@pytest.mark.anyio
async def test_passkey_puzzle_assert_member(cost_logger: CostLogger) -> None:
async with sim_and_client() as (sim, client):
delegated_puzzle = Program.to(1)
delegated_puzzle_hash = delegated_puzzle.get_tree_hash()

# setup keys
seed = 0x1A62C9636D1C9DB2E7D564D0C11603BF456AAD25AA7B12BDFD762B4E38E7EDC6
secp_sk = ec.derive_private_key(seed, ec.SECP256R1(), default_backend())
secp_pk = secp_sk.public_key().public_bytes(Encoding.X962, PublicFormat.CompressedPoint)

passkey_member = PasskeyPuzzleAssertMember(secp_pk, sim.defaults.GENESIS_CHALLENGE)

passkey_puzzle = PuzzleWithRestrictions(0, [], passkey_member)

# Farm and find coin
await sim.farm_block(passkey_puzzle.puzzle_hash())
coin = (
await client.get_coin_records_by_puzzle_hashes([passkey_puzzle.puzzle_hash()], include_spent_coins=False)
)[0].coin
block_height = sim.block_height

# Create an announcements to be asserted in the delegated puzzle
announcement = CreateCoinAnnouncement(msg=b"foo", coin_id=coin.name())

# Get signature for AGG_SIG_ME
authenticator_data = b"foo"
client_data = {"challenge": passkey_member.create_message(delegated_puzzle_hash, coin.puzzle_hash)}
client_data_hash = std_hash(PasskeyMember.format_client_data_as_str(client_data).encode("utf8"))
signature_message = authenticator_data + client_data_hash
der_sig = secp_sk.sign(
signature_message,
# The type stubs are weird here, `deterministic_signing` is assuredly an argument
ec.ECDSA(hashes.SHA256(), deterministic_signing=True), # type: ignore[call-arg]
)
r, s = decode_dss_signature(der_sig)
sig = r.to_bytes(32, byteorder="big") + s.to_bytes(32, byteorder="big")
sb = WalletSpendBundle(
[
make_spend(
coin,
passkey_puzzle.puzzle_reveal(),
passkey_puzzle.solve(
[],
[],
passkey_member.solve(
authenticator_data,
client_data,
sig,
coin.puzzle_hash,
),
DelegatedPuzzleAndSolution(
delegated_puzzle,
Program.to(
[
announcement.to_program(),
announcement.corresponding_assertion().to_program(),
]
),
),
),
)
],
G2Element(),
)
result = await client.push_tx(
cost_logger.add_cost(
"Passkey w/ puzzle assert spendbundle",
sb,
)
)
assert result == (MempoolInclusionStatus.SUCCESS, None)
await sim.farm_block()
await sim.rewind(block_height)


@pytest.mark.anyio
async def test_secp256r1_member(cost_logger: CostLogger) -> None:
async with sim_and_client() as (sim, client):
Expand Down
17 changes: 13 additions & 4 deletions chia/wallet/puzzles/custody/member_puzzles/member_puzzles.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
PASSKEY_MEMBER_MOD = load_clvm_maybe_recompile(
"passkey_member.clsp", package_or_requirement="chia.wallet.puzzles.custody.member_puzzles"
)
PASSKEY_PUZZLE_ASSERT_MEMBER_MOD = load_clvm_maybe_recompile(
"passkey_member_puzzle_assert.clsp", package_or_requirement="chia.wallet.puzzles.custody.member_puzzles"
)

SECPR1_MEMBER_MOD = load_clvm_maybe_recompile(
"secp256r1_member.clsp", package_or_requirement="chia.wallet.puzzles.custody.member_puzzles"
Expand Down Expand Up @@ -78,19 +81,25 @@ def puzzle(self, nonce: int) -> Program:
def puzzle_hash(self, nonce: int) -> bytes32:
return self.puzzle(nonce).get_tree_hash()

def create_message(self, delegated_puzzle_hash: bytes32, coin_id: bytes32) -> str:
message = base64.urlsafe_b64encode(std_hash(delegated_puzzle_hash + coin_id + self.genesis_challenge))
def create_message(self, delegated_puzzle_hash: bytes32, asserted_info: bytes32) -> str:
message = base64.urlsafe_b64encode(std_hash(delegated_puzzle_hash + asserted_info + self.genesis_challenge))
return message.decode("utf-8").rstrip("=")

@staticmethod
def format_client_data_as_str(client_data: dict[str, Any]) -> str:
return json.dumps(client_data, separators=(",", ":"))

def solve(
self, authenticator_data: bytes, client_data: dict[str, Any], signature: bytes, coin_id: bytes32
self, authenticator_data: bytes, client_data: dict[str, Any], signature: bytes, asserted_info: bytes32
) -> Program:
json_str = PasskeyMember.format_client_data_as_str(client_data)
return Program.to([authenticator_data, json_str, json_str.find('"challenge":'), signature, coin_id])
return Program.to([authenticator_data, json_str, json_str.find('"challenge":'), signature, asserted_info])


@dataclass(frozen=True)
class PasskeyPuzzleAssertMember(PasskeyMember):
def puzzle(self, nonce: int) -> Program:
return PASSKEY_PUZZLE_ASSERT_MEMBER_MOD.curry(self.genesis_challenge, self.secp_pk)


@dataclass(frozen=True)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
; member puzzle with SECP256-R1 signature provided by a passkey (ie Yubikey)

(mod (GENESIS_CHALLENGE SECP_PK
Delegated_Puzzle_Hash
; The WebAuthn authenticator data.
; See https://www.w3.org/TR/webauthn-2/#dom-authenticatorassertionresponse-authenticatordata.
authenticator_data
; The WebAuthn client data JSON.
; See https://www.w3.org/TR/webauthn-2/#dom-authenticatorresponse-clientdatajson.
client_data_json
; The index at which "challenge":"..." occurs in `clientDataJSON`.
challenge_index
; the signature returned by the authenticator
signature
; my puzzle hash
puzzle_hash
)

(include *standard-cl-23*)
(include condition_codes.clib)
(include sha256tree.clib)

(defconstant b64-charset "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_")

(defun map-triples (fun n blob)
(if (any (= n (strlen blob)) (> n (strlen blob)))
()
(c (a fun (list (substr blob n (+ n 3)))) (map-triples fun (+ n 3) blob))
)
)

(defun flat-map (fun lst)
(if lst
(if (f lst)
(c (a fun (list (f (f lst)))) (flat-map fun (c (r (f lst)) (r lst))))
(flat-map fun (r lst))
)
()
)
)

(defun lookup-b64 (byte)
(substr b64-charset byte (+ byte 1))
)

(defun-inline trim-padding (plen output)
(substr output 0 (- (strlen output) plen))
)

(defun-inline convert (int)
(if (> int -1) int (+ int 256))
)

(defun b64-encode-blob (blob)
(assign
pad_mod (r (divmod (strlen blob) 3))
padding (if pad_mod (- 3 pad_mod) 0)
bytes (concat blob (substr 0x000000 0 padding))
sextets
(map-triples (lambda ((& padding) bytes)
(assign
(fb_upper . fb_lower) (divmod (convert (substr bytes 0 1)) 4)
(sb_upper . sb_lower) (divmod (convert (substr bytes 1 2)) 16)
(tb_upper . tb_lower) (divmod (convert (substr bytes 2 3)) 64)
(list
fb_upper
(logior (ash fb_lower 4) sb_upper)
(logior (ash sb_lower 2) tb_upper)
tb_lower
)
)
)
0
bytes
)
(trim-padding padding (a (c (list 14) (flat-map lookup-b64 sextets)) ()))
)
)

(assign
message (b64-encode-blob (sha256 Delegated_Puzzle_Hash puzzle_hash GENESIS_CHALLENGE))
challenge (concat '"challenge":"' message '"')
(if (= (substr client_data_json challenge_index (+ challenge_index (strlen challenge))) challenge)
(c
(list ASSERT_MY_PUZZLEHASH puzzle_hash)
(secp256r1_verify SECP_PK (sha256 authenticator_data (sha256 client_data_json)) signature)
)
(x)
)
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ff02ffff01ff02ff3effff04ff02ffff04ff03ffff04ffff02ff2cffff04ff02ffff04ffff0bff17ff8202ffff0580ff80808080ff8080808080ffff04ffff01ffffffff02ffff03ffff21ffff09ff0bffff0dff178080ffff15ff0bffff0dff17808080ffff01ff0180ffff01ff04ffff02ff05ffff04ffff0cff17ff0bffff10ff0bffff01038080ff808080ffff02ff10ffff04ff02ffff04ff05ffff04ffff10ff0bffff010380ffff04ff17ff8080808080808080ff0180ff02ffff03ff0bffff01ff02ffff03ff13ffff01ff04ffff02ff05ffff04ff23ff808080ffff02ff18ffff04ff02ffff04ff05ffff04ffff04ff33ff1b80ff808080808080ffff01ff02ff18ffff04ff02ffff04ff05ffff04ff1bff808080808080ff0180ffff01ff018080ff0180ffff0cffff01c0404142434445464748494a4b4c4d4e4f505152535455565758595a6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5fff05ffff10ff05ffff01018080ffff02ff3cffff04ff02ffff04ff03ffff04ffff06ffff14ffff0dff0580ffff01038080ff8080808080ff02ff12ffff04ff02ffff04ff03ffff04ffff02ffff03ff0bffff01ff11ffff0103ff0b80ffff01ff018080ff0180ff8080808080ffffff02ff2affff04ff02ffff04ff03ffff04ffff0eff11ffff0cffff0183000000ff80ff0b8080ff8080808080ffff02ff2effff04ff02ffff04ff03ffff04ffff02ff10ffff04ff02ffff04ffff04ffff0102ffff04ffff04ffff0101ffff04ffff0102ffff04ffff04ffff0101ff1680ffff04ffff04ffff0104ffff04ffff04ffff0101ff0280ffff04ffff0101ff80808080ff8080808080ffff04ffff04ffff0104ffff04ffff04ffff0101ffff04ff15ff808080ffff04ffff0101ff80808080ff80808080ffff04ff80ffff04ff0bff808080808080ff8080808080ff04ff4fffff04ffff19ffff16ff6fffff010480ff2780ffff04ffff19ffff16ff37ffff010280ff1380ffff04ff1bff8080808080ffff02ff3affff04ff02ffff04ffff04ffff04ff09ff8080ffff04ff0bff808080ffff04ffff14ffff02ffff03ffff15ffff0cff0bffff0102ffff010380ffff0181ff80ffff01ff0cff0bffff0102ffff010380ffff01ff10ffff0cff0bffff0102ffff010380ffff018201008080ff0180ffff014080ffff04ffff14ffff02ffff03ffff15ffff0cff0bffff0101ffff010280ffff0181ff80ffff01ff0cff0bffff0101ffff010280ffff01ff10ffff0cff0bffff0101ffff010280ffff018201008080ff0180ffff011080ffff04ffff14ffff02ffff03ffff15ffff0cff0bff80ffff010180ffff0181ff80ffff01ff0cff0bff80ffff010180ffff01ff10ffff0cff0bff80ffff010180ffff018201008080ff0180ffff010480ff80808080808080ffff0cffff02ffff04ffff04ffff010eff8080ffff02ff18ffff04ff02ffff04ffff04ffff0102ffff04ffff04ffff0101ff1480ffff04ffff04ffff0104ffff04ffff04ffff0101ff0280ffff04ffff0101ff80808080ff80808080ffff04ff0bff808080808080ff8080ff80ffff11ffff0dffff02ffff04ffff04ffff010eff8080ffff02ff18ffff04ff02ffff04ffff04ffff0102ffff04ffff04ffff0101ff1480ffff04ffff04ffff0104ffff04ffff04ffff0101ff0280ffff04ffff0101ff80808080ff80808080ffff04ff0bff808080808080ff808080ff298080ff02ffff03ffff09ffff0cff8200bdff82017dffff10ff82017dffff0dffff0effff018d226368616c6c656e6765223a22ff0bffff012280808080ffff0effff018d226368616c6c656e6765223a22ff0bffff01228080ffff01ff04ffff04ffff0148ffff04ff8205fdff808080ffff841c3a8f00ff15ffff0bff5dffff0bff8200bd8080ff8202fd8080ffff01ff088080ff0180ff018080
1 change: 1 addition & 0 deletions chia/wallet/puzzles/deployed_puzzle_hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"p2_singleton_or_delayed_puzhash": "adb656e0211e2ab4f42069a4c5efc80dc907e7062be08bf1628c8e5b6d94d25b",
"p2_singleton_via_delegated_puzzle": "9590eaa169e45b655a31d3c06bbd355a3e2b2e3e410d3829748ce08ab249c39e",
"passkey_member": "2877c080c18a408111ec86b108da56dd667f968ce38f87623ca084934127059c",
"passkey_member_puzzle_assert": "0bf2d91cf0442a139b2cb9f9c2a68a3ac3b497093fc66595b3c4c398ae94d206",
"pool_member_innerpuz": "a8490702e333ddd831a3ac9c22d0fa26d2bfeaf2d33608deb22f0e0123eb0494",
"pool_waitingroom_innerpuz": "a317541a765bf8375e1c6e7c13503d0d2cbf56cacad5182befe947e78e2c0307",
"restrictions": "a28d59d39f964a93159c986b1914694f6f2f1c9901178f91e8b0ba4045980eef",
Expand Down

0 comments on commit 4652380

Please sign in to comment.