From d8bf3ebf2266dd5d845a869a351011829413515d Mon Sep 17 00:00:00 2001 From: Hank Stoever Date: Tue, 13 Feb 2024 17:01:47 -0800 Subject: [PATCH] feat: allow signer-key allowances instead of signature --- stacks-signer/src/main.rs | 2 +- stackslib/src/chainstate/stacks/boot/mod.rs | 6 +- .../src/chainstate/stacks/boot/pox-4.clar | 72 +++++++++++++++---- .../src/chainstate/stacks/boot/pox_4_tests.rs | 14 ++-- .../src/util_lib/signed_structured_data.rs | 9 --- testnet/stacks-node/src/mockamoto.rs | 4 +- .../src/tests/nakamoto_integrations.rs | 6 +- 7 files changed, 74 insertions(+), 39 deletions(-) diff --git a/stacks-signer/src/main.rs b/stacks-signer/src/main.rs index aa4498294..7ba74b55a 100644 --- a/stacks-signer/src/main.rs +++ b/stacks-signer/src/main.rs @@ -416,7 +416,7 @@ pub mod tests { let program = format!( r#" {} - (verify-signer-key-sig {} u{} "{}" u{} 0x{} 0x{}) + (verify-signer-key-sig {} u{} "{}" u{} (some 0x{}) 0x{}) "#, &*POX_4_CODE, //s Value::Tuple(pox_addr.clone().as_clarity_tuple().unwrap()), //p diff --git a/stackslib/src/chainstate/stacks/boot/mod.rs b/stackslib/src/chainstate/stacks/boot/mod.rs index f969558f9..8431a9936 100644 --- a/stackslib/src/chainstate/stacks/boot/mod.rs +++ b/stackslib/src/chainstate/stacks/boot/mod.rs @@ -1860,7 +1860,7 @@ pub mod test { addr_tuple, Value::UInt(burn_ht as u128), Value::UInt(lock_period), - Value::buff_from(signature).unwrap(), + Value::some(Value::buff_from(signature).unwrap()).unwrap(), Value::buff_from(signer_key.to_bytes_compressed()).unwrap(), ], ) @@ -2009,7 +2009,7 @@ pub mod test { vec![ Value::UInt(lock_period), addr_tuple, - Value::buff_from(signature).unwrap(), + Value::some(Value::buff_from(signature).unwrap()).unwrap(), Value::buff_from(signer_key.to_bytes_compressed()).unwrap(), ], ) @@ -2114,7 +2114,7 @@ pub mod test { vec![ addr_tuple, Value::UInt(reward_cycle), - Value::buff_from(signature).unwrap(), + Value::some(Value::buff_from(signature).unwrap()).unwrap(), Value::buff_from(signer_key.to_bytes_compressed()).unwrap(), ], ) diff --git a/stackslib/src/chainstate/stacks/boot/pox-4.clar b/stackslib/src/chainstate/stacks/boot/pox-4.clar index d8655250d..559d1cad6 100644 --- a/stackslib/src/chainstate/stacks/boot/pox-4.clar +++ b/stackslib/src/chainstate/stacks/boot/pox-4.clar @@ -211,6 +211,19 @@ ;; for the given reward cycle (define-map aggregate-public-keys uint (buff 33)) +;; State for setting allowances for signer keys to be used in +;; certain stacking transactions +(define-map signer-key-allowances + { + signer-key: (buff 33), + reward-cycle: uint, + period: uint, + topic: (string-ascii 12), + pox-addr: { version: (buff 1), hashbytes: (buff 32) }, + } + bool +) + ;; What's the reward cycle number of the burnchain block height? ;; Will runtime-abort if height is less than the first burnchain block (this is intentional) (define-read-only (burn-height-to-reward-cycle (height uint)) @@ -576,7 +589,7 @@ (pox-addr (tuple (version (buff 1)) (hashbytes (buff 32)))) (start-burn-ht uint) (lock-period uint) - (signer-sig (buff 65)) + (signer-sig (optional (buff 65))) (signer-key (buff 33))) ;; this stacker's first reward cycle is the _next_ reward cycle (let ((first-reward-cycle (+ u1 (current-pox-reward-cycle))) @@ -704,21 +717,30 @@ ;; See `get-signer-key-message-hash` for details on the message hash. ;; ;; Note that `reward-cycle` corresponds to the _current_ reward cycle, -;; not the reward cycle at which the delegation will start. -;; The public key is recovered from the signature and compared to `signer-key`. +;; when used with `stack-stx` and `stack-extend`. +;; When `signer-sig` is present, the public key is recovered from the signature +;; and compared to `signer-key`. +;; If `signer-sig` is `none`, the function verifies that an allowance was previously +;; added for this key. (define-read-only (verify-signer-key-sig (pox-addr { version: (buff 1), hashbytes: (buff 32) }) (reward-cycle uint) (topic (string-ascii 12)) (period uint) - (signer-sig (buff 65)) + (signer-sig-opt (optional (buff 65))) (signer-key (buff 33))) - (ok (asserts! - (is-eq - (unwrap! (secp256k1-recover? - (get-signer-key-message-hash pox-addr reward-cycle topic period) - signer-sig) (err ERR_INVALID_SIGNATURE_RECOVER)) - signer-key) - (err ERR_INVALID_SIGNATURE_PUBKEY)))) + (match signer-sig-opt + signer-sig (ok (asserts! + (is-eq + (unwrap! (secp256k1-recover? + (get-signer-key-message-hash pox-addr reward-cycle topic period) + signer-sig) (err ERR_INVALID_SIGNATURE_RECOVER)) + signer-key) + (err ERR_INVALID_SIGNATURE_PUBKEY))) + (begin + (ok (asserts! (default-to false (map-get? signer-key-allowances + { signer-key: signer-key, reward-cycle: reward-cycle, period: period, topic: topic, pox-addr: pox-addr })) + (err ERR_NOT_ALLOWED))) + ))) ;; Commit partially stacked STX and allocate a new PoX reward address slot. ;; This allows a stacker/delegate to lock fewer STX than the minimal threshold in multiple transactions, @@ -734,7 +756,7 @@ ;; *New in Stacks 2.1.* (define-private (inner-stack-aggregation-commit (pox-addr { version: (buff 1), hashbytes: (buff 32) }) (reward-cycle uint) - (signer-sig (buff 65)) + (signer-sig (optional (buff 65))) (signer-key (buff 33))) (let ((partial-stacked ;; fetch the partial commitments @@ -777,7 +799,7 @@ ;; Returns (err ...) on failure. (define-public (stack-aggregation-commit (pox-addr { version: (buff 1), hashbytes: (buff 32) }) (reward-cycle uint) - (signer-sig (buff 65)) + (signer-sig (optional (buff 65))) (signer-key (buff 33))) (match (inner-stack-aggregation-commit pox-addr reward-cycle signer-sig signer-key) pox-addr-index (ok true) @@ -787,7 +809,7 @@ ;; *New in Stacks 2.1.* (define-public (stack-aggregation-commit-indexed (pox-addr { version: (buff 1), hashbytes: (buff 32) }) (reward-cycle uint) - (signer-sig (buff 65)) + (signer-sig (optional (buff 65))) (signer-key (buff 33))) (inner-stack-aggregation-commit pox-addr reward-cycle signer-sig signer-key)) @@ -1036,7 +1058,7 @@ ;; used for signing. The `tx-sender` can thus decide to change the key when extending. (define-public (stack-extend (extend-count uint) (pox-addr { version: (buff 1), hashbytes: (buff 32) }) - (signer-sig (buff 65)) + (signer-sig (optional (buff 65))) (signer-key (buff 33))) (let ((stacker-info (stx-account tx-sender)) ;; to extend, there must already be an etry in the stacking-state @@ -1300,6 +1322,26 @@ (ok { stacker: stacker, unlock-burn-height: new-unlock-ht })))) +;; Add an allowance for a signer key. +;; When an allowance is added, the `signer-sig` argument is not required +;; in the functions that use it as an argument. +;; The `allowed` flag can be used to either enable or disable the allowance. +;; Only the Stacks principal associated with `signer-key` can call this function. +;; *New in Stacks 3.0* +(define-public (set-signer-key-allowance (pox-addr { version: (buff 1), hashbytes: (buff 32)}) + (period uint) + (reward-cycle uint) + (topic (string-ascii 12)) + (signer-key (buff 33)) + (allowed bool)) + (begin + ;; Validate that `tx-sender` has the same pubkey hash as `signer-key` + (asserts! (is-eq + (unwrap! (principal-construct? (if is-in-mainnet 0x16 0x1a) (hash160 signer-key)) (err ERR_INVALID_SIGNER_KEY)) + tx-sender) (err ERR_NOT_ALLOWED)) + (map-set signer-key-allowances { pox-addr: pox-addr, period: period, reward-cycle: reward-cycle, topic: topic, signer-key: signer-key } allowed) + (ok true))) + ;; Get the _current_ PoX stacking delegation information for a stacker. If the information ;; is expired, or if there's never been such a stacker, then returns none. ;; *New in Stacks 2.1* diff --git a/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs b/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs index 99a3db0b7..2148d9370 100644 --- a/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs +++ b/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs @@ -1470,7 +1470,7 @@ fn verify_signer_key_sig( LimitedCostTracker::new_free(), |env| { let program = format!( - "(verify-signer-key-sig {} u{} \"{}\" u{} 0x{} 0x{})", + "(verify-signer-key-sig {} u{} \"{}\" u{} (some 0x{}) 0x{})", Value::Tuple(pox_addr.clone().as_clarity_tuple().unwrap()), reward_cycle, topic.get_name_str(), @@ -2268,7 +2268,7 @@ fn stack_stx_signer_key() { pox_addr_val.clone(), Value::UInt(block_height as u128), Value::UInt(2), - Value::buff_from(signature.clone()).unwrap(), + Value::some(Value::buff_from(signature.clone()).unwrap()).unwrap(), signer_key_val.clone(), ], )]; @@ -2384,7 +2384,7 @@ fn stack_extend_signer_key() { vec![ Value::UInt(1), pox_addr_val.clone(), - Value::buff_from(signature.clone()).unwrap(), + Value::some(Value::buff_from(signature.clone()).unwrap()).unwrap(), signer_extend_key_val.clone(), ], )]; @@ -2492,7 +2492,7 @@ fn delegate_stack_stx_signer_key() { vec![ pox_addr_val.clone(), Value::UInt(next_reward_cycle.into()), - Value::buff_from(signature).unwrap(), + Value::some(Value::buff_from(signature).unwrap()).unwrap(), signer_key_val.clone(), ], ), @@ -2661,7 +2661,7 @@ fn delegate_stack_stx_extend_signer_key() { vec![ pox_addr.as_clarity_tuple().unwrap().into(), Value::UInt(next_reward_cycle.into()), - Value::buff_from(signature).unwrap(), + Value::some(Value::buff_from(signature).unwrap()).unwrap(), signer_key_val.clone(), ], ); @@ -2681,7 +2681,7 @@ fn delegate_stack_stx_extend_signer_key() { vec![ pox_addr.as_clarity_tuple().unwrap().into(), Value::UInt(extend_cycle.into()), - Value::buff_from(extend_signature).unwrap(), + Value::some(Value::buff_from(extend_signature).unwrap()).unwrap(), signer_extend_key_val.clone(), ], ); @@ -2896,7 +2896,7 @@ fn delegate_stack_increase() { vec![ pox_addr.as_clarity_tuple().unwrap().into(), Value::UInt(next_reward_cycle.into()), - Value::buff_from(signature).unwrap(), + (Value::some(Value::buff_from(signature).unwrap()).unwrap()), signer_key_val.clone(), ], ); diff --git a/stackslib/src/util_lib/signed_structured_data.rs b/stackslib/src/util_lib/signed_structured_data.rs index 3405acf75..019443842 100644 --- a/stackslib/src/util_lib/signed_structured_data.rs +++ b/stackslib/src/util_lib/signed_structured_data.rs @@ -250,15 +250,6 @@ pub mod pox4 { CHAIN_ID_TESTNET, lock_period, ); - println!( - "Hash: 0x{}", - to_hex(expected_hash_vec.as_bytes().as_slice()) - ); - println!( - "Pubkey: {}", - to_hex(pubkey.to_bytes_compressed().as_slice()) - ); - // println!("PoxAddr: {}", pox_addr_b58_serialize(&pox_addr).unwrap()); let expected_hash = expected_hash_vec.as_bytes(); // Test 1: valid result diff --git a/testnet/stacks-node/src/mockamoto.rs b/testnet/stacks-node/src/mockamoto.rs index 77a0993b8..037ff0225 100644 --- a/testnet/stacks-node/src/mockamoto.rs +++ b/testnet/stacks-node/src/mockamoto.rs @@ -865,7 +865,7 @@ impl MockamotoNode { pox_address.as_clarity_tuple().unwrap().into(), ClarityValue::UInt(u128::from(parent_burn_height)), ClarityValue::UInt(12), - ClarityValue::buff_from(signature).unwrap(), + ClarityValue::some(ClarityValue::buff_from(signature).unwrap()).unwrap(), ClarityValue::buff_from(signer_key).unwrap(), ], }) @@ -889,7 +889,7 @@ impl MockamotoNode { function_args: vec![ ClarityValue::UInt(5), pox_address.as_clarity_tuple().unwrap().into(), - ClarityValue::buff_from(signature).unwrap(), + ClarityValue::some(ClarityValue::buff_from(signature).unwrap()).unwrap(), ClarityValue::buff_from(signer_key).unwrap(), ], }) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 38c2a7441..e82a9d284 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -416,7 +416,8 @@ pub fn boot_to_epoch_3( pox_addr_tuple.clone(), clarity::vm::Value::UInt(205), clarity::vm::Value::UInt(12), - clarity::vm::Value::buff_from(signature).unwrap(), + clarity::vm::Value::some(clarity::vm::Value::buff_from(signature).unwrap()) + .unwrap(), clarity::vm::Value::buff_from(signer_pk.to_bytes_compressed()).unwrap(), ], ); @@ -988,7 +989,8 @@ fn correct_burn_outs() { pox_addr_tuple, clarity::vm::Value::UInt(pox_info.current_burnchain_block_height.into()), clarity::vm::Value::UInt(1), - clarity::vm::Value::buff_from(signature).unwrap(), + clarity::vm::Value::some(clarity::vm::Value::buff_from(signature).unwrap()) + .unwrap(), clarity::vm::Value::buff_from(pk_bytes).unwrap(), ], );