Skip to content

Commit 91b9c21

Browse files
authored
Calculate clear signing messages correctly (#355)
* Calculate clear signing messages correctly * New messages * Bindings
1 parent b315827 commit 91b9c21

File tree

7 files changed

+238
-85
lines changed

7 files changed

+238
-85
lines changed

bindings/clear_signing.json

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
"fields": {
66
"launcher_id": "Bytes32",
77
"custody_hash": "TreeHash",
8-
"delegated_spend": "Spend",
9-
"coin_id": "Option<Bytes32>"
8+
"delegated_spend": "Spend"
109
}
1110
},
1211
"VaultTransaction": {
@@ -22,8 +21,7 @@
2221
"total_fee": "u64",
2322
"reserved_fee": "u64",
2423
"p2_puzzle_hash": "Bytes32",
25-
"coin_message_hash": "Option<Bytes32>",
26-
"puzzle_message_hash": "Bytes32"
24+
"delegated_puzzle_hash": "Bytes32"
2725
}
2826
},
2927
"ParsedPayment": {
@@ -80,5 +78,33 @@
8078
"puzzle_hash": "Bytes32",
8179
"amount": "u64"
8280
}
81+
},
82+
"calculate_vault_puzzle_message": {
83+
"type": "function",
84+
"args": {
85+
"delegated_puzzle_hash": "Bytes32",
86+
"vault_puzzle_hash": "Bytes32"
87+
},
88+
"return": "Bytes"
89+
},
90+
"calculate_vault_coin_message": {
91+
"type": "function",
92+
"args": {
93+
"delegated_puzzle_hash": "Bytes32",
94+
"vault_coin_id": "Bytes32",
95+
"genesis_challenge": "Bytes32"
96+
},
97+
"return": "Bytes"
98+
},
99+
"calculate_vault_start_recovery_message": {
100+
"type": "function",
101+
"args": {
102+
"delegated_puzzle_hash": "Bytes32",
103+
"left_side_subtree_hash": "Bytes32",
104+
"recovery_timelock": "u64",
105+
"vault_coin_id": "Bytes32",
106+
"genesis_challenge": "Bytes32"
107+
},
108+
"return": "Bytes"
83109
}
84110
}

crates/chia-sdk-bindings/src/clear_signing.rs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use chia_protocol::Bytes32;
1+
use bindy::Result;
2+
use chia_protocol::{Bytes, Bytes32};
23
use chia_sdk_driver::{self as sdk};
34
use clvm_utils::TreeHash;
45

@@ -9,7 +10,6 @@ pub struct VaultSpendReveal {
910
pub launcher_id: Bytes32,
1011
pub custody_hash: TreeHash,
1112
pub delegated_spend: Spend,
12-
pub coin_id: Option<Bytes32>,
1313
}
1414

1515
impl From<VaultSpendReveal> for sdk::VaultSpendReveal {
@@ -18,7 +18,44 @@ impl From<VaultSpendReveal> for sdk::VaultSpendReveal {
1818
launcher_id: value.launcher_id,
1919
custody_hash: value.custody_hash,
2020
delegated_spend: value.delegated_spend.into(),
21-
coin_id: value.coin_id,
2221
}
2322
}
2423
}
24+
25+
pub fn calculate_vault_puzzle_message(
26+
delegated_puzzle_hash: Bytes32,
27+
vault_puzzle_hash: Bytes32,
28+
) -> Result<Bytes> {
29+
Ok(sdk::calculate_vault_puzzle_message(
30+
delegated_puzzle_hash,
31+
vault_puzzle_hash,
32+
))
33+
}
34+
35+
pub fn calculate_vault_coin_message(
36+
delegated_puzzle_hash: Bytes32,
37+
vault_coin_id: Bytes32,
38+
genesis_challenge: Bytes32,
39+
) -> Result<Bytes> {
40+
Ok(sdk::calculate_vault_coin_message(
41+
delegated_puzzle_hash,
42+
vault_coin_id,
43+
genesis_challenge,
44+
))
45+
}
46+
47+
pub fn calculate_vault_start_recovery_message(
48+
delegated_puzzle_hash: Bytes32,
49+
left_side_subtree_hash: Bytes32,
50+
recovery_timelock: u64,
51+
vault_coin_id: Bytes32,
52+
genesis_challenge: Bytes32,
53+
) -> Result<Bytes> {
54+
Ok(sdk::calculate_vault_start_recovery_message(
55+
delegated_puzzle_hash,
56+
left_side_subtree_hash,
57+
recovery_timelock,
58+
vault_coin_id,
59+
genesis_challenge,
60+
))
61+
}

crates/chia-sdk-driver/src/clear_signing.rs

Lines changed: 121 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
use std::collections::HashSet;
22

3-
use chia_protocol::{Bytes32, Coin, CoinSpend};
3+
use chia_consensus::opcodes::{
4+
CREATE_COIN_ANNOUNCEMENT, CREATE_PUZZLE_ANNOUNCEMENT, RECEIVE_MESSAGE, SEND_MESSAGE,
5+
};
6+
use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend};
47
use chia_puzzle_types::{
58
Memos,
69
nft::NftMetadata,
710
offer::{NotarizedPayment, SettlementPaymentsSolution},
8-
singleton::SingletonArgs,
911
};
1012
use chia_puzzles::SETTLEMENT_PAYMENT_HASH;
1113
use chia_sdk_types::{
12-
Condition, MessageFlags, MessageSide, Mod, announcement_id, conditions::CreateCoin,
13-
puzzles::SingletonMember, run_puzzle, tree_hash_notarized_payment,
14+
Condition, MessageFlags, MessageSide, Mod, announcement_id,
15+
conditions::CreateCoin,
16+
puzzles::{
17+
AddDelegatedPuzzleWrapper, Force1of2RestrictedVariable, PreventConditionOpcode,
18+
PreventMultipleCreateCoinsMod, SingletonMember, Timelock,
19+
},
20+
run_puzzle, tree_hash_notarized_payment,
1421
};
15-
use chia_sha2::Sha256;
1622
use clvm_traits::{FromClvm, ToClvm};
17-
use clvm_utils::{TreeHash, tree_hash};
23+
use clvm_utils::{ToTreeHash, TreeHash, tree_hash};
1824
use clvmr::{Allocator, NodePtr};
1925

2026
use crate::{BURN_PUZZLE_HASH, Cat, ClawbackV2, DriverError, Nft, Puzzle, Spend, mips_puzzle_hash};
@@ -31,8 +37,6 @@ pub struct VaultSpendReveal {
3137
/// The delegated puzzle we're signing and its solution.
3238
/// Its output is the non-custody related conditions that the vault spend will output.
3339
pub delegated_spend: Spend,
34-
/// The coin id of the vault coin that is being spent (used for calculating the non-fast forwardable message hash).
35-
pub coin_id: Option<Bytes32>,
3640
}
3741

3842
/// The purpose of this is to provide sufficient information to verify what is happening to a vault and its assets
@@ -63,11 +67,8 @@ pub struct VaultTransaction {
6367
pub reserved_fee: u64,
6468
/// The p2 puzzle hash that the vault owns (analogous to a decoded XCH or TXCH address).
6569
pub p2_puzzle_hash: Bytes32,
66-
/// The hash of the delegated puzzle hash and the vault coin id, used for non-fast forwardable spend signing.
67-
/// This is only calculated if the vault coin id is provided.
68-
pub coin_message_hash: Option<Bytes32>,
69-
/// The hash of the delegated puzzle hash and the vault puzzle hash, used for fast forwardable spend signing.
70-
pub puzzle_message_hash: Bytes32,
70+
/// The original delegated puzzle hash that is being signed for.
71+
pub delegated_puzzle_hash: Bytes32,
7172
}
7273

7374
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -444,24 +445,7 @@ impl VaultTransaction {
444445
}
445446
}
446447

447-
let delegated_puzzle_hash = tree_hash(allocator, vault.delegated_spend.puzzle);
448-
449-
let coin_message_hash = if let Some(vault_coin_id) = vault.coin_id {
450-
let mut coin_message_hasher = Sha256::new();
451-
coin_message_hasher.update(delegated_puzzle_hash);
452-
coin_message_hasher.update(vault_coin_id);
453-
Some(coin_message_hasher.finalize().into())
454-
} else {
455-
None
456-
};
457-
458-
let vault_puzzle_hash =
459-
SingletonArgs::curry_tree_hash(vault.launcher_id, vault.custody_hash);
460-
461-
let mut puzzle_message_hasher = Sha256::new();
462-
puzzle_message_hasher.update(delegated_puzzle_hash);
463-
puzzle_message_hasher.update(vault_puzzle_hash);
464-
let puzzle_message_hash = puzzle_message_hasher.finalize().into();
448+
let delegated_puzzle_hash = tree_hash(allocator, vault.delegated_spend.puzzle).into();
465449

466450
Ok(Self {
467451
new_custody_hash,
@@ -472,8 +456,7 @@ impl VaultTransaction {
472456
total_fee: total_input.saturating_sub(total_output),
473457
reserved_fee,
474458
p2_puzzle_hash: our_p2_puzzle_hash,
475-
coin_message_hash,
476-
puzzle_message_hash,
459+
delegated_puzzle_hash,
477460
})
478461
}
479462
}
@@ -767,14 +750,81 @@ fn calculate_transfer_type(
767750
}
768751
}
769752

753+
pub fn calculate_vault_puzzle_message(
754+
delegated_puzzle_hash: Bytes32,
755+
vault_puzzle_hash: Bytes32,
756+
) -> Bytes {
757+
[
758+
delegated_puzzle_hash.to_bytes(),
759+
vault_puzzle_hash.to_bytes(),
760+
]
761+
.concat()
762+
.into()
763+
}
764+
765+
pub fn calculate_vault_coin_message(
766+
delegated_puzzle_hash: Bytes32,
767+
vault_coin_id: Bytes32,
768+
genesis_challenge: Bytes32,
769+
) -> Bytes {
770+
[
771+
delegated_puzzle_hash.to_bytes(),
772+
vault_coin_id.to_bytes(),
773+
genesis_challenge.to_bytes(),
774+
]
775+
.concat()
776+
.into()
777+
}
778+
779+
pub fn calculate_vault_start_recovery_message(
780+
delegated_puzzle_hash: Bytes32,
781+
left_side_subtree_hash: Bytes32,
782+
recovery_timelock: u64,
783+
vault_coin_id: Bytes32,
784+
genesis_challenge: Bytes32,
785+
) -> Bytes {
786+
let mut delegated_puzzle_hash: TreeHash = delegated_puzzle_hash.into();
787+
788+
let restrictions = vec![
789+
Force1of2RestrictedVariable::new(
790+
left_side_subtree_hash,
791+
0,
792+
vec![Timelock::new(recovery_timelock).curry_tree_hash()]
793+
.tree_hash()
794+
.into(),
795+
().tree_hash().into(),
796+
)
797+
.curry_tree_hash(),
798+
PreventConditionOpcode::new(CREATE_COIN_ANNOUNCEMENT).curry_tree_hash(),
799+
PreventConditionOpcode::new(CREATE_PUZZLE_ANNOUNCEMENT).curry_tree_hash(),
800+
PreventConditionOpcode::new(SEND_MESSAGE).curry_tree_hash(),
801+
PreventConditionOpcode::new(RECEIVE_MESSAGE).curry_tree_hash(),
802+
PreventMultipleCreateCoinsMod::mod_hash(),
803+
];
804+
805+
for restriction in restrictions.into_iter().rev() {
806+
delegated_puzzle_hash =
807+
AddDelegatedPuzzleWrapper::new(restriction, delegated_puzzle_hash).curry_tree_hash();
808+
}
809+
810+
[
811+
delegated_puzzle_hash.to_bytes(),
812+
vault_coin_id.to_bytes(),
813+
genesis_challenge.to_bytes(),
814+
]
815+
.concat()
816+
.into()
817+
}
818+
770819
#[cfg(test)]
771820
mod tests {
772821
use super::*;
773822

774823
use anyhow::Result;
824+
use chia_bls::verify;
775825
use chia_puzzles::SINGLETON_LAUNCHER_HASH;
776826
use chia_sdk_test::Simulator;
777-
use chia_sdk_types::Conditions;
827+
use chia_sdk_types::{Conditions, TESTNET11_CONSTANTS};
778828
use rstest::rstest;
779829

780830
use crate::{Action, FeeAction, Id, SpendContext, Spends, TestVault};
@@ -831,7 +881,6 @@ mod tests {
831881
launcher_id: alice.launcher_id(),
832882
custody_hash: alice.custody_hash(),
833883
delegated_spend: result.delegated_spend,
834-
coin_id: None,
835884
};
836885

837886
let tx = VaultTransaction::parse(&mut ctx, &reveal, result.coin_spends)?;
@@ -897,7 +946,6 @@ mod tests {
897946
launcher_id: bob.launcher_id(),
898947
custody_hash: bob.custody_hash(),
899948
delegated_spend: result.delegated_spend,
900-
coin_id: None,
901949
};
902950

903951
let tx = VaultTransaction::parse(&mut ctx, &reveal, result.coin_spends)?;
@@ -933,7 +981,6 @@ mod tests {
933981
launcher_id: bob.launcher_id(),
934982
custody_hash: bob.custody_hash(),
935983
delegated_spend: result.delegated_spend,
936-
coin_id: None,
937984
};
938985

939986
let tx = VaultTransaction::parse(&mut ctx, &reveal, result.coin_spends)?;
@@ -973,7 +1020,6 @@ mod tests {
9731020
launcher_id: alice.launcher_id(),
9741021
custody_hash: alice.custody_hash(),
9751022
delegated_spend: result.delegated_spend,
976-
coin_id: None,
9771023
};
9781024

9791025
let tx = VaultTransaction::parse(&mut ctx, &reveal, result.coin_spends)?;
@@ -1024,7 +1070,6 @@ mod tests {
10241070
launcher_id: alice.launcher_id(),
10251071
custody_hash: alice.custody_hash(),
10261072
delegated_spend: result.delegated_spend,
1027-
coin_id: None,
10281073
};
10291074

10301075
let tx = VaultTransaction::parse(&mut ctx, &reveal, result.coin_spends)?;
@@ -1090,7 +1135,6 @@ mod tests {
10901135
launcher_id: alice.launcher_id(),
10911136
custody_hash: alice.custody_hash(),
10921137
delegated_spend: result.delegated_spend,
1093-
coin_id: None,
10941138
};
10951139

10961140
let tx = VaultTransaction::parse(&mut ctx, &reveal, result.coin_spends)?;
@@ -1117,7 +1161,6 @@ mod tests {
11171161
launcher_id: alice.launcher_id(),
11181162
custody_hash: alice.custody_hash(),
11191163
delegated_spend: result.delegated_spend,
1120-
coin_id: None,
11211164
};
11221165

11231166
let tx = VaultTransaction::parse(&mut ctx, &reveal, result.coin_spends)?;
@@ -1160,7 +1203,6 @@ mod tests {
11601203
launcher_id: alice.launcher_id(),
11611204
custody_hash: alice.custody_hash(),
11621205
delegated_spend: result.delegated_spend,
1163-
coin_id: None,
11641206
};
11651207

11661208
let tx = VaultTransaction::parse(&mut ctx, &reveal, result.coin_spends)?;
@@ -1178,4 +1220,41 @@ mod tests {
11781220

11791221
Ok(())
11801222
}
1223+
1224+
#[rstest]
1225+
fn test_clear_signing_coin_message() -> Result<()> {
1226+
let mut sim = Simulator::new();
1227+
let mut ctx = SpendContext::new();
1228+
1229+
let alice = TestVault::mint(&mut sim, &mut ctx, 1000)?;
1230+
let bob = TestVault::mint(&mut sim, &mut ctx, 0)?;
1231+
1232+
let vault = alice.fetch_vault(&sim)?;
1233+
1234+
let result = alice.spend(
1235+
&mut sim,
1236+
&mut ctx,
1237+
&[Action::send(Id::Xch, bob.puzzle_hash(), 1000, Memos::None)],
1238+
)?;
1239+
1240+
let reveal = VaultSpendReveal {
1241+
launcher_id: alice.launcher_id(),
1242+
custody_hash: alice.custody_hash(),
1243+
delegated_spend: result.delegated_spend,
1244+
};
1245+
1246+
let tx = VaultTransaction::parse(&mut ctx, &reveal, result.coin_spends)?;
1247+
assert_eq!(tx.new_custody_hash, Some(alice.custody_hash()));
1248+
assert_eq!(tx.payments.len(), 1);
1249+
1250+
let coin_message = calculate_vault_coin_message(
1251+
tx.delegated_puzzle_hash,
1252+
vault.coin.coin_id(),
1253+
TESTNET11_CONSTANTS.genesis_challenge,
1254+
);
1255+
1256+
assert!(verify(&result.signature, &alice.public_key, coin_message));
1257+
1258+
Ok(())
1259+
}
11811260
}

0 commit comments

Comments
 (0)