From d8bf3ebf2266dd5d845a869a351011829413515d Mon Sep 17 00:00:00 2001 From: Hank Stoever Date: Tue, 13 Feb 2024 17:01:47 -0800 Subject: [PATCH 1/7] 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 aa4498294b..7ba74b55ad 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 f969558f93..8431a99361 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 d8655250dd..559d1cad66 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 99a3db0b72..2148d9370f 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 3405acf75e..019443842d 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 77a0993b8f..037ff02250 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 38c2a74415..e82a9d284b 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(), ], ); From 8e1c8f5b9bce6895f16d0b92ed5210a147fb0c51 Mon Sep 17 00:00:00 2001 From: Hank Stoever Date: Wed, 14 Feb 2024 12:29:02 -0800 Subject: [PATCH 2/7] feat: add tests for signer-key authorizations --- CHANGELOG.md | 24 +- .../chainstate/nakamoto/coordinator/tests.rs | 6 +- stackslib/src/chainstate/stacks/boot/mod.rs | 70 ++- .../src/chainstate/stacks/boot/pox-4.clar | 34 +- .../src/chainstate/stacks/boot/pox_4_tests.rs | 578 ++++++++++++++++-- stackslib/src/net/tests/mod.rs | 6 +- 6 files changed, 630 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc88952147..96c8b8a17f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE - New [`pox-4` contract](./stackslib/src/chainstate/stacks/boot/pox-4.clar) that reflects changes in how Stackers are signers in Nakamoto: - `stack-stx`, `stack-extend`, and `stack-aggregation-commit` now include a `signer-key` parameter, which represents the public key used by the Signer. This key is used for determining the signer set in Nakamoto. - Functions that include a `signer-key` parameter also include a `signer-sig` parameter to demonstrate that the owner of `signer-key` is approving that particular Stacking operation. For more details, refer to the `verify-signer-key-sig` method in the `pox-4` contract. + - Signer key authorizations can be added via `set-signer-key-authorization` to omit the need for `signer-key` signatures ### Modified @@ -43,23 +44,24 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE - New RPC endpoint at /v2/block_proposal for miner to validate proposed block. Only accessible on local loopback interface -In addition, this introduces a set of improvements to the Stacks miner behavior. In +In addition, this introduces a set of improvements to the Stacks miner behavior. In particular: -* The VRF public key can be re-used across node restarts. -* Settings that affect mining are hot-reloaded from the config file. They take + +- The VRF public key can be re-used across node restarts. +- Settings that affect mining are hot-reloaded from the config file. They take effect once the file is updated; there is no longer a need to restart the -node. -* The act of changing the miner settings in the config file automatically + node. +- The act of changing the miner settings in the config file automatically triggers a subsequent block-build attempt, allowing the operator to force the -miner to re-try building blocks. -* This adds a new tip-selection algorithm that minimizes block orphans within a + miner to re-try building blocks. +- This adds a new tip-selection algorithm that minimizes block orphans within a configurable window of time. -* When configured, the node will automatically stop mining if it is not achieving a +- When configured, the node will automatically stop mining if it is not achieving a targeted win rate over a configurable window of blocks. -* When configured, the node will selectively mine transactions from only certain +- When configured, the node will selectively mine transactions from only certain addresses, or only of certain types (STX-transfers, contract-publishes, -contract-calls). -* When configured, the node will optionally only RBF block-commits if it can + contract-calls). +- When configured, the node will optionally only RBF block-commits if it can produce a block with strictly more transactions. ### Changed diff --git a/stackslib/src/chainstate/nakamoto/coordinator/tests.rs b/stackslib/src/chainstate/nakamoto/coordinator/tests.rs index 395caacc92..b1d5af763b 100644 --- a/stackslib/src/chainstate/nakamoto/coordinator/tests.rs +++ b/stackslib/src/chainstate/nakamoto/coordinator/tests.rs @@ -93,11 +93,11 @@ fn advance_to_nakamoto( &test_stacker.stacker_private_key, 0, test_stacker.amount, - pox_addr.clone(), + &pox_addr, 12, - signing_key, + &signing_key, 34, - signature, + Some(signature), ) }) .collect() diff --git a/stackslib/src/chainstate/stacks/boot/mod.rs b/stackslib/src/chainstate/stacks/boot/mod.rs index 8431a99361..2c5ee5cc4d 100644 --- a/stackslib/src/chainstate/stacks/boot/mod.rs +++ b/stackslib/src/chainstate/stacks/boot/mod.rs @@ -1844,13 +1844,17 @@ pub mod test { key: &StacksPrivateKey, nonce: u64, amount: u128, - addr: PoxAddress, + addr: &PoxAddress, lock_period: u128, - signer_key: StacksPublicKey, + signer_key: &StacksPublicKey, burn_ht: u64, - signature: Vec, + signature_opt: Option>, ) -> StacksTransaction { let addr_tuple = Value::Tuple(addr.as_clarity_tuple().unwrap()); + let signature = match signature_opt { + Some(sig) => Value::some(Value::buff_from(sig).unwrap()).unwrap(), + None => Value::none(), + }; let payload = TransactionPayload::new_contract_call( boot_code_test_addr(), "pox-4", @@ -1860,7 +1864,7 @@ pub mod test { addr_tuple, Value::UInt(burn_ht as u128), Value::UInt(lock_period), - Value::some(Value::buff_from(signature).unwrap()).unwrap(), + signature, Value::buff_from(signer_key.to_bytes_compressed()).unwrap(), ], ) @@ -1993,15 +1997,30 @@ pub mod test { make_tx(key, nonce, 0, payload) } - pub fn make_pox_4_extend( + pub fn make_pox_4_extend_delete_this( key: &StacksPrivateKey, nonce: u64, addr: PoxAddress, lock_period: u128, signer_key: StacksPublicKey, signature: Vec, + ) -> StacksTransaction { + make_pox_4_extend(key, nonce, addr, lock_period, signer_key, Some(signature)) + } + + pub fn make_pox_4_extend( + key: &StacksPrivateKey, + nonce: u64, + addr: PoxAddress, + lock_period: u128, + signer_key: StacksPublicKey, + signature_opt: Option>, ) -> StacksTransaction { let addr_tuple = Value::Tuple(addr.as_clarity_tuple().unwrap()); + let signature = match signature_opt { + Some(sig) => Value::some(Value::buff_from(sig).unwrap()).unwrap(), + None => Value::none(), + }; let payload = TransactionPayload::new_contract_call( boot_code_test_addr(), POX_4_NAME, @@ -2009,7 +2028,7 @@ pub mod test { vec![ Value::UInt(lock_period), addr_tuple, - Value::some(Value::buff_from(signature).unwrap()).unwrap(), + signature, Value::buff_from(signer_key.to_bytes_compressed()).unwrap(), ], ) @@ -2103,10 +2122,14 @@ pub mod test { nonce: u64, pox_addr: &PoxAddress, reward_cycle: u128, - signature: Vec, + signature_opt: Option>, signer_key: &Secp256k1PublicKey, ) -> StacksTransaction { let addr_tuple = Value::Tuple(pox_addr.as_clarity_tuple().unwrap()); + let signature = match signature_opt { + Some(sig) => Value::some(Value::buff_from(sig).unwrap()).unwrap(), + None => Value::none(), + }; let payload = TransactionPayload::new_contract_call( boot_code_test_addr(), POX_4_NAME, @@ -2114,7 +2137,7 @@ pub mod test { vec![ addr_tuple, Value::UInt(reward_cycle), - Value::some(Value::buff_from(signature).unwrap()).unwrap(), + signature, Value::buff_from(signer_key.to_bytes_compressed()).unwrap(), ], ) @@ -2193,6 +2216,37 @@ pub mod test { signature.to_rsv() } + pub fn make_pox_4_set_signer_key_auth( + pox_addr: &PoxAddress, + signer_key: &StacksPrivateKey, + reward_cycle: u128, + topic: &Pox4SignatureTopic, + period: u128, + enabled: bool, + nonce: u64, + sender_key: Option<&StacksPrivateKey>, + ) -> StacksTransaction { + let signer_pubkey = StacksPublicKey::from_private(signer_key); + let payload = TransactionPayload::new_contract_call( + boot_code_test_addr(), + POX_4_NAME, + "set-signer-key-authorization", + vec![ + Value::Tuple(pox_addr.as_clarity_tuple().unwrap()), + Value::UInt(period), + Value::UInt(reward_cycle), + Value::string_ascii_from_bytes(topic.get_name_str().into()).unwrap(), + Value::buff_from(signer_pubkey.to_bytes_compressed()).unwrap(), + Value::Bool(enabled), + ], + ) + .unwrap(); + + let sender_key = sender_key.unwrap_or(signer_key); + + make_tx(sender_key, nonce, 0, payload) + } + fn make_tx( key: &StacksPrivateKey, nonce: u64, diff --git a/stackslib/src/chainstate/stacks/boot/pox-4.clar b/stackslib/src/chainstate/stacks/boot/pox-4.clar index 559d1cad66..f04ee6f1a6 100644 --- a/stackslib/src/chainstate/stacks/boot/pox-4.clar +++ b/stackslib/src/chainstate/stacks/boot/pox-4.clar @@ -213,7 +213,7 @@ ;; State for setting allowances for signer keys to be used in ;; certain stacking transactions -(define-map signer-key-allowances +(define-map signer-key-authorizations { signer-key: (buff 33), reward-cycle: uint, @@ -720,7 +720,8 @@ ;; 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 +;; +;; If `signer-sig` is `none`, the function verifies that an authorization was previously ;; added for this key. (define-read-only (verify-signer-key-sig (pox-addr { version: (buff 1), hashbytes: (buff 32) }) (reward-cycle uint) @@ -729,6 +730,7 @@ (signer-sig-opt (optional (buff 65))) (signer-key (buff 33))) (match signer-sig-opt + ;; `signer-sig` is present, verify the signature signer-sig (ok (asserts! (is-eq (unwrap! (secp256k1-recover? @@ -736,11 +738,11 @@ 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-sig` is not present, verify that an authorization was previously added for this key + (ok (asserts! (default-to false (map-get? signer-key-authorizations { 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, @@ -1322,25 +1324,25 @@ (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 +;; Add an authorization for a signer key. +;; When an authorization 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. +;; The `allowed` flag can be used to either enable or disable the authorization. ;; 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)) +(define-public (set-signer-key-authorization (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))) + (map-set signer-key-authorizations { pox-addr: pox-addr, period: period, reward-cycle: reward-cycle, topic: topic, signer-key: signer-key } allowed) + (ok allowed))) ;; 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. diff --git a/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs b/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs index 2148d9370f..00e82feafe 100644 --- a/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs +++ b/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs @@ -504,14 +504,14 @@ fn pox_extend_transition() { &alice, 2, ALICE_LOCKUP, - PoxAddress::from_legacy( + &PoxAddress::from_legacy( AddressHashMode::SerializeP2PKH, key_to_stacks_addr(&alice).bytes, ), 4, - alice_signer_key, + &alice_signer_key, tip.block_height, - alice_signature, + Some(alice_signature), ); let alice_pox_4_lock_nonce = 2; let alice_first_pox_4_unlock_height = @@ -577,11 +577,11 @@ fn pox_extend_transition() { &bob, 2, BOB_LOCKUP, - bob_pox_addr.clone(), + &bob_pox_addr, 3, - StacksPublicKey::from_private(&bob_signer_private), + &StacksPublicKey::from_private(&bob_signer_private), tip.block_height, - bob_signature, + Some(bob_signature), ); // new signing key needed @@ -603,7 +603,7 @@ fn pox_extend_transition() { alice_pox_addr.clone(), 6, alice_signer_key, - alice_signature, + Some(alice_signature), ); let alice_pox_4_extend_nonce = 3; @@ -870,11 +870,11 @@ fn pox_lock_unlock() { key, 0, 1024 * POX_THRESHOLD_STEPS_USTX, - pox_addr.clone(), + &pox_addr, lock_period, - StacksPublicKey::from_private(&signer_key), + &StacksPublicKey::from_private(&signer_key), tip_height, - signature, + Some(signature), )); pox_addr }) @@ -1688,11 +1688,11 @@ fn stack_stx_verify_signer_sig() { &stacker_key, stacker_nonce, min_ustx, - pox_addr.clone(), + &pox_addr, lock_period, - signer_public_key.clone(), + &signer_public_key, block_height, - signature, + Some(signature), ); // test 2: invalid pox addr @@ -1709,11 +1709,11 @@ fn stack_stx_verify_signer_sig() { &stacker_key, stacker_nonce, min_ustx, - pox_addr.clone(), + &pox_addr, lock_period, - signer_public_key.clone(), + &signer_public_key, block_height, - signature, + Some(signature), ); // Test 3: invalid key used to sign @@ -1730,11 +1730,11 @@ fn stack_stx_verify_signer_sig() { &stacker_key, stacker_nonce, min_ustx, - pox_addr.clone(), + &pox_addr, lock_period, - signer_public_key.clone(), + &signer_public_key, block_height, - signature, + Some(signature), ); // Test 4: invalid topic @@ -1751,11 +1751,11 @@ fn stack_stx_verify_signer_sig() { &stacker_key, stacker_nonce, min_ustx, - pox_addr.clone(), + &pox_addr, lock_period, - signer_public_key.clone(), + &signer_public_key, block_height, - signature, + Some(signature), ); // Test 5: invalid period @@ -1772,11 +1772,11 @@ fn stack_stx_verify_signer_sig() { &stacker_key, stacker_nonce, min_ustx, - pox_addr.clone(), + &pox_addr, lock_period, - signer_public_key.clone(), + &signer_public_key, block_height, - signature, + Some(signature), ); // Test 6: valid signature @@ -1788,11 +1788,11 @@ fn stack_stx_verify_signer_sig() { &stacker_key, stacker_nonce, min_ustx, - pox_addr, + &pox_addr, lock_period, - signer_public_key.clone(), + &signer_public_key, block_height, - signature, + Some(signature), ); let txs = vec![ @@ -1857,11 +1857,11 @@ fn stack_extend_verify_sig() { &stacker_key, stacker_nonce, min_ustx, - pox_addr.clone(), + &pox_addr, lock_period, - signer_public_key.clone(), + &signer_public_key, block_height, - signature, + Some(signature), ); // We need a new signer-key for the extend tx @@ -1884,7 +1884,7 @@ fn stack_extend_verify_sig() { pox_addr.clone(), lock_period, signer_public_key.clone(), - signature, + Some(signature), ); // Test 2: invalid pox-addr @@ -1904,7 +1904,7 @@ fn stack_extend_verify_sig() { pox_addr.clone(), lock_period, signer_public_key.clone(), - signature, + Some(signature), ); // Test 3: invalid key used to sign @@ -1919,7 +1919,7 @@ fn stack_extend_verify_sig() { pox_addr.clone(), lock_period, signer_public_key.clone(), - signature, + Some(signature), ); // Test 4: valid stack-extend @@ -1933,7 +1933,7 @@ fn stack_extend_verify_sig() { pox_addr, lock_period, signer_public_key.clone(), - signature, + Some(signature), ); peer.tenure_with_txs( @@ -2034,7 +2034,7 @@ fn stack_agg_commit_verify_sig() { delegate_nonce, &pox_addr, next_reward_cycle, - signature, + Some(signature), &signer_pk, ); @@ -2054,7 +2054,7 @@ fn stack_agg_commit_verify_sig() { delegate_nonce, &pox_addr, next_reward_cycle, - signature, + Some(signature), &signer_pk, ); @@ -2068,7 +2068,7 @@ fn stack_agg_commit_verify_sig() { delegate_nonce, &pox_addr, next_reward_cycle, - signature, + Some(signature), &signer_pk, ); @@ -2087,7 +2087,7 @@ fn stack_agg_commit_verify_sig() { delegate_nonce, &pox_addr, next_reward_cycle, - signature, + Some(signature), &signer_pk, ); @@ -2106,7 +2106,7 @@ fn stack_agg_commit_verify_sig() { delegate_nonce, &pox_addr, next_reward_cycle, - signature, + Some(signature), &signer_pk, ); @@ -2120,7 +2120,7 @@ fn stack_agg_commit_verify_sig() { delegate_nonce, &pox_addr, next_reward_cycle, - signature, + Some(signature), &signer_pk, ); @@ -2314,6 +2314,444 @@ fn stack_stx_signer_key() { ); } +#[test] +/// Test `stack-stx` using signer key authorization +fn stack_stx_signer_auth() { + let observer = TestEventObserver::new(); + let (burnchain, mut peer, keys, latest_block, block_height, mut coinbase_nonce) = + prepare_pox4_test(function_name!(), Some(&observer)); + + let mut stacker_nonce = 0; + let stacker_key = &keys[0]; + let min_ustx = get_stacking_minimum(&mut peer, &latest_block); + let signer_nonce = 0; + let signer_key = &keys[1]; + let signer_public_key = StacksPublicKey::from_private(signer_key); + let signer_key_val = Value::buff_from(signer_public_key.to_bytes_compressed()).unwrap(); + + let reward_cycle = get_current_reward_cycle(&peer, &burnchain); + + let pox_addr = pox_addr_from(&stacker_key); + let pox_addr_val = Value::Tuple(pox_addr.clone().as_clarity_tuple().unwrap()); + let lock_period = 6; + + let topic = Pox4SignatureTopic::StackStx; + + let failed_stack_nonce = stacker_nonce; + let failed_stack_tx = make_pox_4_lockup( + &stacker_key, + stacker_nonce, + min_ustx, + &pox_addr, + lock_period, + &signer_public_key, + block_height, + None, + ); + + let enable_auth_nonce = signer_nonce; + let enable_auth_tx = make_pox_4_set_signer_key_auth( + &pox_addr, + &signer_key, + reward_cycle, + &topic, + lock_period, + true, + signer_nonce, + None, + ); + + // Ensure that stack-stx succeeds with auth + stacker_nonce += 1; + let successful_stack_nonce = stacker_nonce; + let valid_stack_tx = make_pox_4_lockup( + &stacker_key, + stacker_nonce, + min_ustx, + &pox_addr, + lock_period, + &signer_public_key, + block_height, + None, + ); + + let txs = vec![failed_stack_tx, enable_auth_tx, valid_stack_tx]; + + let latest_block = peer.tenure_with_txs(&txs, &mut coinbase_nonce); + let stacking_state = get_stacking_state_pox_4( + &mut peer, + &latest_block, + &key_to_stacks_addr(stacker_key).to_account_principal(), + ) + .expect("No stacking state, stack-stx failed") + .expect_tuple(); + + let stacker_txs = + get_last_block_sender_transactions(&observer, key_to_stacks_addr(&stacker_key)); + + let expected_error = Value::error(Value::Int(19)).unwrap(); + + assert_eq!(stacker_txs.len(), (stacker_nonce + 1) as usize); + let stacker_tx_result = + |nonce: u64| -> Value { stacker_txs.get(nonce as usize).unwrap().result.clone() }; + + // First stack-stx failed + assert_eq!(stacker_tx_result(failed_stack_nonce), expected_error); + + let successful_stack_result = stacker_tx_result(successful_stack_nonce); + // second stack-stx worked + successful_stack_result + .expect_result_ok() + .expect("Expected ok result from stack-stx tx"); + + let signer_txs = get_last_block_sender_transactions(&observer, key_to_stacks_addr(&signer_key)); + + // enable auth worked + let enable_tx_result = signer_txs + .get(enable_auth_nonce as usize) + .unwrap() + .result + .clone(); + assert_eq!(enable_tx_result, Value::okay_true()); +} + +#[test] +/// Test `stack-aggregation-commit` using signer key authorization +fn stack_agg_commit_signer_auth() { + let lock_period = 2; + let observer = TestEventObserver::new(); + let (burnchain, mut peer, keys, latest_block, block_height, coinbase_nonce) = + prepare_pox4_test(function_name!(), Some(&observer)); + + let mut coinbase_nonce = coinbase_nonce; + + let mut delegate_nonce = 0; + let stacker_nonce = 0; + let min_ustx = get_stacking_minimum(&mut peer, &latest_block); + + let stacker_key = &keys[0]; + let stacker_addr = PrincipalData::from(key_to_stacks_addr(&stacker_key)); + + let signer_sk = &keys[1]; + let signer_pk = StacksPublicKey::from_private(signer_sk); + + let delegate_key = &keys[2]; + let delegate_addr = key_to_stacks_addr(&delegate_key); + + let pox_addr = pox_addr_from(&delegate_key); + + let reward_cycle = burnchain + .block_height_to_reward_cycle(block_height) + .unwrap() as u128; + let next_reward_cycle = reward_cycle + 1; + + // Setup: delegate-stx and delegate-stack-stx + + let delegate_tx = make_pox_4_delegate_stx( + &stacker_key, + stacker_nonce, + min_ustx, + delegate_addr.clone().into(), + None, + None, + ); + + let delegate_stack_stx_nonce = delegate_nonce; + let delegate_stack_stx_tx = make_pox_4_delegate_stack_stx( + &delegate_key, + delegate_nonce, + stacker_addr, + min_ustx, + pox_addr.clone(), + block_height.into(), + lock_period, + ); + + let topic = Pox4SignatureTopic::AggregationCommit; + + // Stack agg failes without auth + delegate_nonce += 1; + let invalid_agg_nonce = delegate_nonce; + let invalid_agg_tx = make_pox_4_aggregation_commit_indexed( + &delegate_key, + delegate_nonce, + &pox_addr, + next_reward_cycle, + None, + &signer_pk, + ); + + // Signer enables auth + let enable_auth_nonce = 0; + let enable_auth_tx = make_pox_4_set_signer_key_auth( + &pox_addr, + &signer_sk, + next_reward_cycle, + &topic, + 1, + true, + enable_auth_nonce, + None, + ); + + // Stack agg works with auth + delegate_nonce += 1; + let valid_agg_nonce = delegate_nonce; + let valid_agg_tx = make_pox_4_aggregation_commit_indexed( + &delegate_key, + delegate_nonce, + &pox_addr, + next_reward_cycle, + None, + &signer_pk, + ); + + let txs = vec![ + delegate_tx, + delegate_stack_stx_tx, + invalid_agg_tx, + enable_auth_tx, + valid_agg_tx, + ]; + + let latest_block = peer.tenure_with_txs(&txs, &mut coinbase_nonce); + + let delegate_txs = get_last_block_sender_transactions(&observer, delegate_addr); + + let tx_result = + |nonce: u64| -> Value { delegate_txs.get(nonce as usize).unwrap().result.clone() }; + + let expected_error = Value::error(Value::Int(19)).unwrap(); + assert_eq!(tx_result(invalid_agg_nonce), expected_error); + let successful_agg_result = tx_result(valid_agg_nonce); + successful_agg_result + .expect_result_ok() + .expect("Expected ok result from stack-agg-commit tx"); +} + +#[test] +/// Test `stack-extend` using signer key authorization +/// instead of signatures +fn stack_extend_signer_auth() { + let lock_period = 2; + let observer = TestEventObserver::new(); + let (burnchain, mut peer, keys, latest_block, block_height, coinbase_nonce) = + prepare_pox4_test(function_name!(), Some(&observer)); + + let mut coinbase_nonce = coinbase_nonce; + + let mut stacker_nonce = 0; + let stacker_key = &keys[0]; + let min_ustx = get_stacking_minimum(&mut peer, &latest_block); + let stacker_addr = key_to_stacks_addr(&stacker_key); + let signer_key = &keys[1]; + let signer_public_key = StacksPublicKey::from_private(signer_key); + let pox_addr = pox_addr_from(&signer_key); + + let reward_cycle = get_current_reward_cycle(&peer, &burnchain); + let topic = Pox4SignatureTopic::StackExtend; + + // Setup: stack-stx + let signature = make_signer_key_signature( + &pox_addr, + &signer_key, + reward_cycle, + &Pox4SignatureTopic::StackStx, + lock_period, + ); + let stack_nonce = stacker_nonce; + let stack_tx = make_pox_4_lockup( + &stacker_key, + stacker_nonce, + min_ustx, + &pox_addr, + lock_period, + &signer_public_key, + block_height, + Some(signature), + ); + + // Stack-extend should fail without auth + stacker_nonce += 1; + let invalid_extend_nonce = stacker_nonce; + let invalid_cycle_tx = make_pox_4_extend( + &stacker_key, + stacker_nonce, + pox_addr.clone(), + lock_period, + signer_public_key.clone(), + None, + ); + + // Enable authorization + let enable_auth_nonce = 0; + let enable_auth_tx = make_pox_4_set_signer_key_auth( + &pox_addr, + &signer_key, + reward_cycle, + &topic, + lock_period, + true, + enable_auth_nonce, + None, + ); + + // Stack-extend should work with auth + stacker_nonce += 1; + let valid_extend_nonce = stacker_nonce; + let valid_tx = make_pox_4_extend( + &stacker_key, + stacker_nonce, + pox_addr, + lock_period, + signer_public_key.clone(), + None, + ); + + let txs = vec![stack_tx, invalid_cycle_tx, enable_auth_tx, valid_tx]; + + let latest_block = peer.tenure_with_txs(&txs, &mut coinbase_nonce); + + let stacker_txs = get_last_block_sender_transactions(&observer, stacker_addr); + + let tx_result = + |nonce: u64| -> Value { stacker_txs.get(nonce as usize).unwrap().result.clone() }; + + let expected_error = Value::error(Value::Int(19)).unwrap(); + assert_eq!(tx_result(invalid_extend_nonce), expected_error); + + let valid_extend_tx_result = tx_result(valid_extend_nonce); + valid_extend_tx_result + .expect_result_ok() + .expect("Expected ok result from stack-extend tx"); +} + +#[test] +/// Test `set-signer-key-authorization` function +fn test_set_signer_key_auth() { + let lock_period = 2; + let observer = TestEventObserver::new(); + let (burnchain, mut peer, keys, latest_block, block_height, coinbase_nonce) = + prepare_pox4_test(function_name!(), Some(&observer)); + + let mut coinbase_nonce = coinbase_nonce; + + let alice_nonce = 0; + let alice_key = &keys[0]; + let min_ustx = get_stacking_minimum(&mut peer, &latest_block); + let alice_addr = key_to_stacks_addr(&alice_key); + let mut signer_nonce = 0; + let signer_key = &keys[1]; + let signer_public_key = StacksPublicKey::from_private(signer_key); + let pox_addr = pox_addr_from(&signer_key); + + // Only the address associated with `signer-key` can enable auth for that key + let invalid_enable_nonce = alice_nonce; + let invalid_enable_tx = make_pox_4_set_signer_key_auth( + &pox_addr, + &signer_key, + 1, + &Pox4SignatureTopic::StackStx, + lock_period, + true, + invalid_enable_nonce, + Some(&alice_key), + ); + + // Disable auth for `signer-key` + let disable_auth_nonce = signer_nonce; + let disable_auth_tx: StacksTransaction = make_pox_4_set_signer_key_auth( + &pox_addr, + &signer_key, + 1, + &Pox4SignatureTopic::StackStx, + lock_period, + false, + disable_auth_nonce, + None, + ); + + let latest_block = + peer.tenure_with_txs(&[invalid_enable_tx, disable_auth_tx], &mut coinbase_nonce); + + let alice_txs = get_last_block_sender_transactions(&observer, alice_addr); + let invalid_enable_tx_result = alice_txs + .get(invalid_enable_nonce as usize) + .unwrap() + .result + .clone(); + let expected_error = Value::error(Value::Int(19)).unwrap(); + assert_eq!(invalid_enable_tx_result, expected_error); + + let signer_key_enabled = get_signer_key_authorization_pox_4( + &mut peer, + &latest_block, + &pox_addr, + 1, + &Pox4SignatureTopic::StackStx, + lock_period.try_into().unwrap(), + &signer_public_key, + ); + + assert_eq!(signer_key_enabled.unwrap(), false); + + // Next block, enable the key + signer_nonce += 1; + let enable_auth_nonce = signer_nonce; + let enable_auth_tx = make_pox_4_set_signer_key_auth( + &pox_addr, + &signer_key, + 1, + &Pox4SignatureTopic::StackStx, + lock_period, + true, + enable_auth_nonce, + None, + ); + + let latest_block = peer.tenure_with_txs(&[enable_auth_tx], &mut coinbase_nonce); + + let signer_key_enabled = get_signer_key_authorization_pox_4( + &mut peer, + &latest_block, + &pox_addr, + 1, + &Pox4SignatureTopic::StackStx, + lock_period.try_into().unwrap(), + &signer_public_key, + ); + + assert_eq!(signer_key_enabled.unwrap(), true); + + // Next block, re-disable the key authorization + signer_nonce += 1; + let disable_auth_nonce = signer_nonce; + let disable_auth_tx = make_pox_4_set_signer_key_auth( + &pox_addr, + &signer_key, + 1, + &Pox4SignatureTopic::StackStx, + lock_period, + false, + disable_auth_nonce, + None, + ); + + let latest_block = peer.tenure_with_txs(&[disable_auth_tx], &mut coinbase_nonce); + + let signer_key_enabled = get_signer_key_authorization_pox_4( + &mut peer, + &latest_block, + &pox_addr, + 1, + &Pox4SignatureTopic::StackStx, + lock_period.try_into().unwrap(), + &signer_public_key, + ); + + assert_eq!(signer_key_enabled.unwrap(), false); +} + #[test] fn stack_extend_signer_key() { let lock_period = 2; @@ -2355,11 +2793,11 @@ fn stack_extend_signer_key() { &stacker_key, stacker_nonce, min_ustx, - pox_addr.clone(), + &pox_addr, lock_period, - signer_key, + &signer_key, block_height, - signature, + Some(signature), )]; stacker_nonce += 1; @@ -2754,11 +3192,11 @@ fn stack_increase() { alice_stacking_private_key, alice_nonce, min_ustx, - pox_addr.clone(), + &pox_addr, lock_period, - signing_pk, + &signing_pk, block_height as u64, - signature, + Some(signature), ); // Initial tx arr includes a stack_stx pox_4 helper found in mod.rs @@ -2959,6 +3397,52 @@ pub fn get_stacking_state_pox_4( }) } +pub fn get_signer_key_authorization_pox_4( + peer: &mut TestPeer, + tip: &StacksBlockId, + pox_addr: &PoxAddress, + reward_cycle: u64, + topic: &Pox4SignatureTopic, + period: u128, + signer_key: &StacksPublicKey, +) -> Option { + with_clarity_db_ro(peer, tip, |db| { + let lookup_tuple = TupleData::from_data(vec![ + ( + "pox-addr".into(), + pox_addr.as_clarity_tuple().unwrap().into(), + ), + ("reward-cycle".into(), Value::UInt(reward_cycle.into())), + ( + "topic".into(), + Value::string_ascii_from_bytes(topic.get_name_str().into()).unwrap(), + ), + ("period".into(), Value::UInt(period.into())), + ( + "signer-key".into(), + Value::buff_from(signer_key.to_bytes_compressed()).unwrap(), + ), + ]) + .unwrap() + .into(); + let epoch = db.get_clarity_epoch_version().unwrap(); + let map_entry = db + .fetch_entry_unknown_descriptor( + &boot_code_id(boot::POX_4_NAME, false), + "signer-key-authorizations", + &lookup_tuple, + &epoch, + ) + .unwrap() + .expect_optional() + .unwrap(); + match map_entry { + Some(v) => Some(v.expect_bool().unwrap()), + None => None, + } + }) +} + pub fn get_partially_stacked_state_pox_4( peer: &mut TestPeer, tip: &StacksBlockId, diff --git a/stackslib/src/net/tests/mod.rs b/stackslib/src/net/tests/mod.rs index 44c19298e6..db731d3c2b 100644 --- a/stackslib/src/net/tests/mod.rs +++ b/stackslib/src/net/tests/mod.rs @@ -334,11 +334,11 @@ impl NakamotoBootPlan { &test_stacker.stacker_private_key, 0, test_stacker.amount, - pox_addr, + &pox_addr, 12, - StacksPublicKey::from_private(&test_stacker.signer_private_key), + &StacksPublicKey::from_private(&test_stacker.signer_private_key), 34, - signature, + Some(signature), ) }) .collect(); From e22aa760b5391853a51e1b0230d53372b3b2a5a8 Mon Sep 17 00:00:00 2001 From: Hank Stoever Date: Mon, 19 Feb 2024 07:51:15 -0800 Subject: [PATCH 3/7] fix: remove unused function left from refactoring --- stackslib/src/chainstate/stacks/boot/mod.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/stackslib/src/chainstate/stacks/boot/mod.rs b/stackslib/src/chainstate/stacks/boot/mod.rs index 2c5ee5cc4d..5e3b026914 100644 --- a/stackslib/src/chainstate/stacks/boot/mod.rs +++ b/stackslib/src/chainstate/stacks/boot/mod.rs @@ -1997,17 +1997,6 @@ pub mod test { make_tx(key, nonce, 0, payload) } - pub fn make_pox_4_extend_delete_this( - key: &StacksPrivateKey, - nonce: u64, - addr: PoxAddress, - lock_period: u128, - signer_key: StacksPublicKey, - signature: Vec, - ) -> StacksTransaction { - make_pox_4_extend(key, nonce, addr, lock_period, signer_key, Some(signature)) - } - pub fn make_pox_4_extend( key: &StacksPrivateKey, nonce: u64, From 99c576569667abf268a70645d3ee9183b5d2b35c Mon Sep 17 00:00:00 2001 From: Hank Stoever Date: Mon, 19 Feb 2024 08:11:33 -0800 Subject: [PATCH 4/7] feat: comment and use constants in `set-signer-key-authorization` --- .../src/chainstate/stacks/boot/pox-4.clar | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/stackslib/src/chainstate/stacks/boot/pox-4.clar b/stackslib/src/chainstate/stacks/boot/pox-4.clar index f04ee6f1a6..c0d89be9a4 100644 --- a/stackslib/src/chainstate/stacks/boot/pox-4.clar +++ b/stackslib/src/chainstate/stacks/boot/pox-4.clar @@ -44,6 +44,11 @@ (define-constant ADDRESS_VERSION_NATIVE_P2WPKH 0x04) (define-constant ADDRESS_VERSION_NATIVE_P2WSH 0x05) (define-constant ADDRESS_VERSION_NATIVE_P2TR 0x06) + +;; Values for stacks address versions +(define-constant STACKS_ADDR_VERSION_MAINNET 0x16) +(define-constant STACKS_ADDR_VERSION_TESTNET 0x1a) + ;; Keep these constants in lock-step with the address version buffs above ;; Maximum value of an address version as a uint (define-constant MAX_ADDRESS_VERSION u6) @@ -211,17 +216,29 @@ ;; 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 +;; State for setting authorizations for signer keys to be used in +;; certain stacking transactions. These fields match the fields used +;; in the message hash for signature-based signer key authorizations. +;; Values in this map are set in `set-signer-key-authorization`. (define-map signer-key-authorizations { + ;; The signer key being authorized signer-key: (buff 33), + ;; The reward cycle for which the authorization is valid. + ;; For `stack-stx` and `stack-extend`, this refers to the reward + ;; cycle where the transaction is confirmed. For `stack-aggregation-commit`, + ;; this refers to the reward cycle argument in that function. reward-cycle: uint, + ;; For `stack-stx`, this refers to `lock-period`. For `stack-extend`, + ;; this refers to `extend-count`. For `stack-aggregation-commit`, this is `u1`. period: uint, + ;; A string representing the function where this authorization is valid. Either + ;; `stack-stx`, `stack-extend`, or `agg-commit`. topic: (string-ascii 12), + ;; The PoX address that can be used with this signer key pox-addr: { version: (buff 1), hashbytes: (buff 32) }, } - bool + bool ;; Whether the authorization can be used or not ) ;; What's the reward cycle number of the burnchain block height? @@ -1339,7 +1356,7 @@ (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)) + (unwrap! (principal-construct? (if is-in-mainnet STACKS_ADDR_VERSION_MAINNET STACKS_ADDR_VERSION_TESTNET) (hash160 signer-key)) (err ERR_INVALID_SIGNER_KEY)) tx-sender) (err ERR_NOT_ALLOWED)) (map-set signer-key-authorizations { pox-addr: pox-addr, period: period, reward-cycle: reward-cycle, topic: topic, signer-key: signer-key } allowed) (ok allowed))) From 51066ee69ae08546444fac38edb131c4ffe7ceb0 Mon Sep 17 00:00:00 2001 From: Hank Stoever Date: Tue, 20 Feb 2024 06:21:09 -0800 Subject: [PATCH 5/7] feat: improve documentation for signer key authorizations --- .../src/chainstate/stacks/boot/pox-4.clar | 50 ++++++++++++------- .../src/chainstate/stacks/boot/pox_4_tests.rs | 2 +- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/stackslib/src/chainstate/stacks/boot/pox-4.clar b/stackslib/src/chainstate/stacks/boot/pox-4.clar index c0d89be9a4..781709b299 100644 --- a/stackslib/src/chainstate/stacks/boot/pox-4.clar +++ b/stackslib/src/chainstate/stacks/boot/pox-4.clar @@ -220,7 +220,7 @@ ;; certain stacking transactions. These fields match the fields used ;; in the message hash for signature-based signer key authorizations. ;; Values in this map are set in `set-signer-key-authorization`. -(define-map signer-key-authorizations +(define-map signer-key-authorizations { ;; The signer key being authorized signer-key: (buff 33), @@ -237,7 +237,7 @@ topic: (string-ascii 12), ;; The PoX address that can be used with this signer key pox-addr: { version: (buff 1), hashbytes: (buff 32) }, - } + } bool ;; Whether the authorization can be used or not ) @@ -714,17 +714,17 @@ ;; The message hash follows SIP018 for signing structured data. The structured data ;; is the tuple `{ pox-addr: { version, hashbytes }, reward-cycle }`. The domain is ;; `{ name: "pox-4-signer", version: "1.0.0", chain-id: chain-id }`. -(define-read-only (get-signer-key-message-hash (pox-addr { version: (buff 1), hashbytes: (buff 32) }) +(define-read-only (get-signer-key-message-hash (pox-addr { version: (buff 1), hashbytes: (buff 32) }) (reward-cycle uint) (topic (string-ascii 12)) (period uint)) (sha256 (concat SIP018_MSG_PREFIX - (concat + (concat (sha256 (unwrap-panic (to-consensus-buff? { name: "pox-4-signer", version: "1.0.0", chain-id: chain-id }))) (sha256 (unwrap-panic - (to-consensus-buff? { - pox-addr: pox-addr, + (to-consensus-buff? { + pox-addr: pox-addr, reward-cycle: reward-cycle, topic: topic, period: period, @@ -734,11 +734,17 @@ ;; See `get-signer-key-message-hash` for details on the message hash. ;; ;; Note that `reward-cycle` corresponds to the _current_ reward cycle, -;; 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 authorization was previously +;; when used with `stack-stx` and `stack-extend`. Both the reward cycle and +;; the lock period are inflexible, which means that the stacker must confirm their transaction +;; during the exact reward cycle and with the exact period that the signature or authorization was +;; generated for. +;; +;; This function does not verify the payload of the authorization. The caller of +;; this function must ensure that the payload (reward cycle, period, topic, and pox-addr) +;; are valid according to the caller function's requirements. +;; +;; 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 authorization was previously ;; added for this key. (define-read-only (verify-signer-key-sig (pox-addr { version: (buff 1), hashbytes: (buff 32) }) (reward-cycle uint) @@ -748,12 +754,12 @@ (signer-key (buff 33))) (match signer-sig-opt ;; `signer-sig` is present, verify the signature - 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) + 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))) ;; `signer-sig` is not present, verify that an authorization was previously added for this key (ok (asserts! (default-to false (map-get? signer-key-authorizations @@ -1346,6 +1352,14 @@ ;; in the functions that use it as an argument. ;; The `allowed` flag can be used to either enable or disable the authorization. ;; Only the Stacks principal associated with `signer-key` can call this function. +;; +;; Refer to the documentation for `verify-signer-key-sig` for more information +;; regarding the parameters used in an authorization. When the authorization is used +;; in `stack-stx` and `stack-extend`, the `reward-cycle` refers to the reward cycle +;; where the transaction is confirmed, **not** the reward cycle where stacking begins. +;; The `period` parameter must match the exact lock period (or extend count) used +;; in the stacking transaction. +;; ;; *New in Stacks 3.0* (define-public (set-signer-key-authorization (pox-addr { version: (buff 1), hashbytes: (buff 32)}) (period uint) @@ -1355,7 +1369,7 @@ (allowed bool)) (begin ;; Validate that `tx-sender` has the same pubkey hash as `signer-key` - (asserts! (is-eq + (asserts! (is-eq (unwrap! (principal-construct? (if is-in-mainnet STACKS_ADDR_VERSION_MAINNET STACKS_ADDR_VERSION_TESTNET) (hash160 signer-key)) (err ERR_INVALID_SIGNER_KEY)) tx-sender) (err ERR_NOT_ALLOWED)) (map-set signer-key-authorizations { pox-addr: pox-addr, period: period, reward-cycle: reward-cycle, topic: topic, signer-key: signer-key } allowed) diff --git a/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs b/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs index 00e82feafe..d9857c55d6 100644 --- a/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs +++ b/stackslib/src/chainstate/stacks/boot/pox_4_tests.rs @@ -2469,7 +2469,7 @@ fn stack_agg_commit_signer_auth() { let topic = Pox4SignatureTopic::AggregationCommit; - // Stack agg failes without auth + // Stack agg fails without auth delegate_nonce += 1; let invalid_agg_nonce = delegate_nonce; let invalid_agg_tx = make_pox_4_aggregation_commit_indexed( From 059ae88deae783081abdf159debcd2bd8d62e200 Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Sun, 11 Feb 2024 16:24:49 -0500 Subject: [PATCH 6/7] chore: Use `jemalloc` global allocator on supported platforms --- Cargo.lock | 22 ++++++++++++++++++++++ Cargo.toml | 1 + stackslib/Cargo.toml | 3 +++ stackslib/src/main.rs | 7 +++++++ testnet/stacks-node/Cargo.toml | 3 +++ testnet/stacks-node/src/main.rs | 7 +++++++ 6 files changed, 43 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 4ea7bc8652..74c4a5efc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3512,6 +3512,7 @@ dependencies = [ "stacks-signer", "stackslib", "stx-genesis", + "tikv-jemallocator", "tokio", "toml 0.5.11", "tracing", @@ -3593,6 +3594,7 @@ dependencies = [ "stacks-common", "stdext", "stx-genesis", + "tikv-jemallocator", "time 0.2.27", "url", "winapi 0.3.9", @@ -3806,6 +3808,26 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.4+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965fe0c26be5c56c94e38ba547249074803efd52adfb66de62107d95aab3eaca" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "time" version = "0.2.27" diff --git a/Cargo.toml b/Cargo.toml index b6606497c4..66791df99c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ hashbrown = "0.14.3" rand_core = "0.6" rand = "0.8" rand_chacha = "0.3.1" +tikv-jemallocator = "0.5.4" wsts = { version = "8.1", default-features = false } # Use a bit more than default optimization for diff --git a/stackslib/Cargo.toml b/stackslib/Cargo.toml index 40ddd8e847..ba67b5c1fe 100644 --- a/stackslib/Cargo.toml +++ b/stackslib/Cargo.toml @@ -59,6 +59,9 @@ siphasher = "0.3.7" wsts = { workspace = true } hashbrown = { workspace = true } +[target.'cfg(not(target_env = "msvc"))'.dependencies] +tikv-jemallocator = {workspace = true} + [target.'cfg(unix)'.dependencies] nix = "0.23" diff --git a/stackslib/src/main.rs b/stackslib/src/main.rs index d7c9fcd356..f6a901e5ec 100644 --- a/stackslib/src/main.rs +++ b/stackslib/src/main.rs @@ -26,6 +26,13 @@ extern crate stacks_common; #[macro_use(o, slog_log, slog_trace, slog_debug, slog_info, slog_warn, slog_error)] extern crate slog; +#[cfg(not(target_env = "msvc"))] +use tikv_jemallocator::Jemalloc; + +#[cfg(not(target_env = "msvc"))] +#[global_allocator] +static GLOBAL: Jemalloc = Jemalloc; + use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; use std::fs::{File, OpenOptions}; diff --git a/testnet/stacks-node/Cargo.toml b/testnet/stacks-node/Cargo.toml index e4739e0c0b..5d9d964a1f 100644 --- a/testnet/stacks-node/Cargo.toml +++ b/testnet/stacks-node/Cargo.toml @@ -32,6 +32,9 @@ rand = { workspace = true } rand_core = { workspace = true } hashbrown = { workspace = true } +[target.'cfg(not(target_env = "msvc"))'.dependencies] +tikv-jemallocator = {workspace = true} + [dev-dependencies] ring = "0.16.19" warp = "0.3.5" diff --git a/testnet/stacks-node/src/main.rs b/testnet/stacks-node/src/main.rs index 3418ed9726..08da97dd53 100644 --- a/testnet/stacks-node/src/main.rs +++ b/testnet/stacks-node/src/main.rs @@ -58,6 +58,13 @@ use crate::mockamoto::MockamotoNode; use crate::neon_node::{BlockMinerThread, TipCandidate}; use crate::run_loop::boot_nakamoto; +#[cfg(not(target_env = "msvc"))] +use tikv_jemallocator::Jemalloc; + +#[cfg(not(target_env = "msvc"))] +#[global_allocator] +static GLOBAL: Jemalloc = Jemalloc; + /// Implmentation of `pick_best_tip` CLI option fn cli_pick_best_tip(config_path: &str, at_stacks_height: Option) -> TipCandidate { info!("Loading config at path {}", config_path); From d5ef455b4025071f95b04d6553cbcb6688385173 Mon Sep 17 00:00:00 2001 From: Hank Stoever Date: Wed, 21 Feb 2024 07:01:56 -0800 Subject: [PATCH 7/7] fix: ignore timing out runloop test --- stacks-signer/src/runloop.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/stacks-signer/src/runloop.rs b/stacks-signer/src/runloop.rs index a5a7f12bcc..1804466cb3 100644 --- a/stacks-signer/src/runloop.rs +++ b/stacks-signer/src/runloop.rs @@ -1511,6 +1511,7 @@ mod tests { #[test] #[serial] + #[ignore] fn verify_transactions_valid() { let config = Config::load_from_file("./src/tests/conf/signer-0.toml").unwrap(); let mut runloop: RunLoop> = RunLoop::from(&config);