From 3c1f1d0168c5c1e02b7d88b1937156a4d2c90a70 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 5 Mar 2024 12:48:24 -0500 Subject: [PATCH] Fix spending more than 2 CATs at once --- Cargo.lock | 37 ++++++-- Cargo.toml | 1 + src/spends/cat/raw_spend.rs | 180 +++++++++++++++++++++++++++++++++++- wallet.crt | 19 ++++ wallet.key | 28 ++++++ 5 files changed, 254 insertions(+), 11 deletions(-) create mode 100644 wallet.crt create mode 100644 wallet.key diff --git a/Cargo.lock b/Cargo.lock index 8331b6c5..716dd137 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -208,6 +208,24 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chia" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89de95a5daa48a2749b7af27188b635d2843f52c8f5c230cab7ac22429413b1f" +dependencies = [ + "chia-protocol", + "chia-traits 0.5.2", + "chia-wallet", + "clvm-derive", + "clvm-traits", + "clvm-utils", + "clvmr", + "hex", + "hex-literal", + "thiserror", +] + [[package]] name = "chia-bls" version = "0.4.0" @@ -234,7 +252,7 @@ dependencies = [ "anyhow", "arbitrary", "blst", - "chia-traits 0.5.1", + "chia-traits 0.5.2", "hex", "hkdf", "sha2 0.10.8", @@ -249,7 +267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5ffc39ab1aa7e333539afe68739eca59c47160a5a3db85b886571b0b3438ad8" dependencies = [ "chia-protocol", - "chia-traits 0.5.1", + "chia-traits 0.5.2", "futures-util", "thiserror", "tokio", @@ -265,7 +283,7 @@ checksum = "10a4f5e87514749bdc03b2a7915e34d8105980f161a29689de321b0dd7c7a835" dependencies = [ "arbitrary", "chia-bls 0.5.1", - "chia-traits 0.5.1", + "chia-traits 0.5.2", "chia_streamable_macro", "clvm-traits", "clvm-utils", @@ -303,9 +321,9 @@ dependencies = [ [[package]] name = "chia-traits" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630c8f3ee351d7a70abd2b6edaae248e5e7e874c24c9251608a9f2e410073de7" +checksum = "a80916358aa139bc9130f147ff784ba8858d41b959d6795e1e91a2c20ccc7409" dependencies = [ "chia_streamable_macro", "hex", @@ -336,6 +354,7 @@ version = "0.6.0" dependencies = [ "bech32", "bip39", + "chia", "chia-bls 0.5.1", "chia-client", "chia-protocol", @@ -372,9 +391,9 @@ dependencies = [ [[package]] name = "clvm-derive" -version = "0.2.14" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9110e638f8a4d34922e0436282c7fe1a8149020aef3aacf699377dcd3f1553da" +checksum = "26b1b1ba82707086cd36e6d36574559270d80f486dfc31be00cad202f842675f" dependencies = [ "proc-macro2", "quote", @@ -383,9 +402,9 @@ dependencies = [ [[package]] name = "clvm-traits" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fdd5089c9e32a4a563da945309c41577181087f5017a3104b031a4e58591e7" +checksum = "421b8c8a1312d55840533b0facae8c0522627e24a9484001374d03055de6df00" dependencies = [ "chia-bls 0.5.1", "clvm-derive", diff --git a/Cargo.toml b/Cargo.toml index c76e1e17..bc27a018 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,5 +31,6 @@ rand_chacha = "0.3.1" [dev-dependencies] bip39 = "2.0.0" +chia = "0.5.2" hex-literal = "0.4.1" once_cell = "1.19.0" diff --git a/src/spends/cat/raw_spend.rs b/src/spends/cat/raw_spend.rs index 59ccc05a..e2cf3f3d 100644 --- a/src/spends/cat/raw_spend.rs +++ b/src/spends/cat/raw_spend.rs @@ -38,6 +38,8 @@ pub fn spend_cat_coins( ) -> Result, ToClvmError> { let mut total_delta = 0; + let len = cat_spends.len(); + cat_spends .iter() .enumerate() @@ -61,8 +63,8 @@ pub fn spend_cat_coins( total_delta += delta; // Find information of neighboring coins on the ring. - let prev_cat = &cat_spends[index.wrapping_sub(1) % cat_spends.len()]; - let next_cat = &cat_spends[index.wrapping_add(1) % cat_spends.len()]; + let prev_cat = &cat_spends[if index == 0 { len - 1 } else { index - 1 }]; + let next_cat = &cat_spends[if index == len - 1 { 0 } else { index + 1 }]; // Construct the puzzle. let puzzle = CurriedProgram { @@ -112,6 +114,10 @@ pub fn spend_cat_coins( #[cfg(test)] mod tests { + use chia::gen::{ + conditions::EmptyVisitor, run_block_generator::run_block_generator, + solution_generator::solution_generator, + }; use chia_bls::{derive_keys::master_to_wallet_unhardened, SecretKey}; use chia_protocol::Bytes32; use chia_wallet::{ @@ -197,4 +203,174 @@ mod tests { ); assert_eq!(hex::encode(actual), hex::encode(expected)); } + + #[test] + fn test_cat_spend_multi() { + let synthetic_key = + master_to_wallet_unhardened(&SecretKey::from_seed(SEED.as_ref()).public_key(), 0) + .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + + let mut a = Allocator::new(); + let standard_puzzle_ptr = node_from_bytes(&mut a, &STANDARD_PUZZLE).unwrap(); + let cat_puzzle_ptr = node_from_bytes(&mut a, &CAT_PUZZLE).unwrap(); + + let asset_id = [42; 32]; + + let p2_puzzle_hash = standard_puzzle_hash(&synthetic_key); + let cat_puzzle_hash = cat_puzzle_hash(asset_id, p2_puzzle_hash); + + let parent_coin_1 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); + let coin_1 = Coin::new( + Bytes32::from(parent_coin_1.coin_id()), + Bytes32::new(cat_puzzle_hash), + 42, + ); + + let parent_coin_2 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); + let coin_2 = Coin::new( + Bytes32::from(parent_coin_2.coin_id()), + Bytes32::new(cat_puzzle_hash), + 34, + ); + + let parent_coin_3 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); + let coin_3 = Coin::new( + Bytes32::from(parent_coin_3.coin_id()), + Bytes32::new(cat_puzzle_hash), + 69, + ); + + let conditions = vec![CatCondition::Normal(Condition::CreateCoin( + CreateCoin::Normal { + puzzle_hash: coin_1.puzzle_hash, + amount: coin_1.amount + coin_2.amount + coin_3.amount, + }, + ))]; + + let coin_spends = spend_cat_coins( + &mut a, + standard_puzzle_ptr, + cat_puzzle_ptr, + asset_id, + &[ + CatSpend { + coin: coin_1, + synthetic_key: synthetic_key.clone(), + conditions, + extra_delta: 0, + lineage_proof: LineageProof { + parent_coin_info: parent_coin_1.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash.into(), + amount: parent_coin_1.amount, + }, + p2_puzzle_hash, + }, + CatSpend { + coin: coin_2, + synthetic_key: synthetic_key.clone(), + conditions: Vec::new(), + extra_delta: 0, + lineage_proof: LineageProof { + parent_coin_info: parent_coin_2.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash.into(), + amount: parent_coin_2.amount, + }, + p2_puzzle_hash, + }, + CatSpend { + coin: coin_3, + synthetic_key, + conditions: Vec::new(), + extra_delta: 0, + lineage_proof: LineageProof { + parent_coin_info: parent_coin_3.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash.into(), + amount: parent_coin_3.amount, + }, + p2_puzzle_hash, + }, + ], + ) + .unwrap(); + + let spend_vec = coin_spends + .clone() + .into_iter() + .map(|coin_spend| { + ( + coin_spend.coin, + coin_spend.puzzle_reveal, + coin_spend.solution, + ) + }) + .collect::>(); + let gen = solution_generator(spend_vec).unwrap(); + let block = + run_block_generator::(&mut a, &gen, &[], u64::MAX, 0).unwrap(); + + assert_eq!(block.cost, 101289468); + + assert_eq!(coin_spends.len(), 3); + + let output_ptr_1 = coin_spends[0] + .puzzle_reveal + .run(&mut a, 0, u64::MAX, &coin_spends[0].solution) + .unwrap() + .1; + let actual = node_to_bytes(&a, output_ptr_1).unwrap(); + + let expected = hex!( + " + ffff46ffa06438c882c2db9f5c2a8b4cbda9258c40a6583b2d7c6becc1678607 + 4d558c834980ffff3cffa1cb1cb6597fe61e67a6cbbcd4e8f0bda5e9fc56cd84 + c9e9502772b410dc8a03207680ffff3dffa0742ddb368882193072ea013bde24 + 4a5c9d40ab4454c09666e84777a79307e17a80ffff32ffb08584adae5630842a + 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc + c9183fe61e48d8bfffa004c476adfcffeacfef7c979bdd03b4641f1870d3f81b + 20636eefbcf879bb64ec80ffff33ffa0f9f2d59294f2aae8f9833db876d1bf43 + 95d46af18c17312041c6f4a4d73fa041ff8200918080 + " + ); + assert_eq!(hex::encode(actual), hex::encode(expected)); + + let output_ptr_2 = coin_spends[1] + .puzzle_reveal + .run(&mut a, 0, u64::MAX, &coin_spends[1].solution) + .unwrap() + .1; + let actual = node_to_bytes(&a, output_ptr_2).unwrap(); + + let expected = hex!( + " + ffff46ffa0ae60b8db0664959078a1c6e51ca6a8fc55207c63a8ac74d026f1d9 + 15c406bac480ffff3cffa1cb9a41843ab318a8336f61a6bf9e8b0b1d555b9f07 + cd19582e0bc52a961c65dc9e80ffff3dffa0294cda8d35164e01c4e3b7c07c36 + a5bb2f38a23e93ef49c882ee74349a0df8bd80ffff32ffb08584adae5630842a + 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc + c9183fe61e48d8bfffa0ba4484b961b7a2369d948d06c55b64bdbfaffb326bc1 + 3b490ab1215dd33d8d468080 + " + ); + assert_eq!(hex::encode(actual), hex::encode(expected)); + + let output_ptr_3 = coin_spends[2] + .puzzle_reveal + .run(&mut a, 0, u64::MAX, &coin_spends[2].solution) + .unwrap() + .1; + let actual = node_to_bytes(&a, output_ptr_3).unwrap(); + + let expected = hex!( + " + ffff46ffa0f8eacbef2bad0c7b27b638a90a37244e75013e977f250230856d05 + a2784e1d0980ffff3cffa1cb17c47c5fa8d795efa0d9227d2066cde36dd4e845 + 7e8f4e507d2015a1c7f3d94b80ffff3dffa0629abc502829339c7880ee003c4e + 68a8181d71206e50e7b36c29301ef60128f580ffff32ffb08584adae5630842a + 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc + c9183fe61e48d8bfffa0ba4484b961b7a2369d948d06c55b64bdbfaffb326bc1 + 3b490ab1215dd33d8d468080 + " + ); + assert_eq!(hex::encode(actual), hex::encode(expected)); + } } diff --git a/wallet.crt b/wallet.crt new file mode 100644 index 00000000..a153e089 --- /dev/null +++ b/wallet.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDLDCCAhSgAwIBAgIUGR3DzdigH5k75jkIx4N1NvdVFsAwDQYJKoZIhvcNAQEL +BQAwRDENMAsGA1UECgwEQ2hpYTEQMA4GA1UEAwwHQ2hpYSBDQTEhMB8GA1UECwwY +T3JnYW5pYyBGYXJtaW5nIERpdmlzaW9uMCAXDTI0MDIyODAzMTkyNloYDzIxMDAw +ODAyMDAwMDAwWjBBMQ0wCwYDVQQDDARDaGlhMQ0wCwYDVQQKDARDaGlhMSEwHwYD +VQQLDBhPcmdhbmljIEZhcm1pbmcgRGl2aXNpb24wggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQC7ee1QHZmSuX5HK/ex0olm7hd1A5mDAS6ExkdzHWaoZE5c +Co+DC+juiqoihbKFMbjiv6NLG3tVTtn4L+bqexX2RkkptxlWk1KIBY1DRXaI1V7v +Cq1ZSncXiD0pAJPDPuSDKhT8uyOkHplL0p3DbV8pJo/yeMILz69kCT/mTglCPwTV +ciY9ybCitwpVygp5HMK7a+JkAZzrAfpMNJ4W+pwFZQIx6l7mQNE1fAGZHtxbRvSY +fOgJsdNR7/TGWIsccwScHaWtAPEUMg4ovwFb5SqFEx2NJbMxzEYMOnJSsT0oQoOx +gFkvZFC7aAtiXOyzr7MYPgWlY3I9fInWSMQ+b6LFAgMBAAGjFzAVMBMGA1UdEQQM +MAqCCGNoaWEubmV0MA0GCSqGSIb3DQEBCwUAA4IBAQCloOT/ngKwW0qUc4xGb8iN +ovsQDOwiyGrMlUDA2Pqw1vicQ8vpHLZfAl5359pXYw2Cov8ueVxeejoSWKJPAqg3 +hh+3lHEcI+tEwAzLfI7JSfNDq5xgJRwjzBbt5bswf0WffJLFBSmbOV6ufsfT86wu +XERBcSumMWbpgCASPqtRBY4e7J57vilgMho+vwPe+eMGYVxRFeTCuPWQYtwxnTTG +wB8LL7e6r+wRRJ+wWTyjKXkyr8xaEen5+wQOQ4ZN8/kfcCMox2Isk67Z/zDdEB8m +RYT37/79qLF+NYa5tPiCnWH2FVJ70c/8Cy3U0dMy0ttQHwXpXhLkpMdxLVjaaiXh +-----END CERTIFICATE----- diff --git a/wallet.key b/wallet.key new file mode 100644 index 00000000..df0c065a --- /dev/null +++ b/wallet.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC7ee1QHZmSuX5H +K/ex0olm7hd1A5mDAS6ExkdzHWaoZE5cCo+DC+juiqoihbKFMbjiv6NLG3tVTtn4 +L+bqexX2RkkptxlWk1KIBY1DRXaI1V7vCq1ZSncXiD0pAJPDPuSDKhT8uyOkHplL +0p3DbV8pJo/yeMILz69kCT/mTglCPwTVciY9ybCitwpVygp5HMK7a+JkAZzrAfpM +NJ4W+pwFZQIx6l7mQNE1fAGZHtxbRvSYfOgJsdNR7/TGWIsccwScHaWtAPEUMg4o +vwFb5SqFEx2NJbMxzEYMOnJSsT0oQoOxgFkvZFC7aAtiXOyzr7MYPgWlY3I9fInW +SMQ+b6LFAgMBAAECggEAGqnvt6pOElExhZvaQb9I7YvFaE4Xwksju5ybwuKWnDKP +9oVF/gRun6a3JevQBSOaQR2yctc4xlxNof/mHslcyUcb1sQE5YxUe6YWfk/d2P5H +30Wse/ucTouA71j2qxlB1OniTEOtgQUdFmyhJY9jrm2eoYTNTHLAZkDuhqnopTQl +AbtnWrUzDQc7cSzeQeH40y40rgevN5B7Bnc6cmB/QZL109GfZtA6zTqLvDFsQ7fz +OTFmVPyBrEfVvELQtTZYBdZ5BKSTEfJEO9+humH2wYn5dtIWvX4NLjrgddBqsP8N +QK3391d8RHK68G2zBg3bejPOPx/ytrlOJxnk4UDawQKBgQDbEgPaa0reB8pseAKX +iiCwc0q61LS1kd1RMMYyJoorccdL6wg+30Qintle2cGZUqhSWk284eTKgU2A8kqy +t7Ae47r2JJ7f++KJVSYaxJw16uBOPpK9Q+2Bl84b8IiEhywLh2MpY/+H6YlnW+Uy +PjSAk1d1Qjzxq809hXPLtDwXcQKBgQDbFHfoERmfDCBuEPcPp6bwOJxrsZEJiP7M +PHbwKnlZM7rzeYQiYJZp1M5jZKvFdd52tQj6XgVfzVMpNCN2A4G+hd0u1EeZOVdz +hRQzkxdgqaspJm7GJt2RY84tAJt0nWoe2yyFlhJtlVZSbDwlB4PjnGcdXcoWXxgN +cADVMELelQKBgDA7VifXqYMa2gFppsBwg+P1bkWWITc8pyqB/5D5FUCda1CWrZkS +FPE+YgNVFB/KqA6BnU3xuiqvrz2tmq6gbKFQaFBbHbOYxHaSDbKlCPOCA+KWOj57 +XyZCIvnZymTLUfIz26w4PQFSdJMSE3MtaOfKCN5jOAHJXoWmBSmbRAOxAoGAditm +OgDvRwDthTEVcpZFz5FZniVbTD9e4J9EsGrYparnjmjfQwPCXjdxU997X7kiU/l2 +bEXGtFQ2JSWAcymVFEV0dsv+CsIJT8Baky8mDve14S5f1fYk0UV6cn+/WnmGQjqK +m0vFiOJie3byQi75jw5P+GEcnlCQ0r61VEkB6LkCgYAzNmUS2FMg2KGjyd/nC61L +f4Y5MT36K3sxfw3JkZfuPcvnnDl1LE7GaHs4QHje9dLObweH4gh+Cm7xApaBozck +or36maTIERjkbhwgaRQoZxrllUGsaIF6JrbIuVbVgEeYFTtUEkcD1L2l0ar2uFEi +qukqXs/BqeeBXgV4T75sOw== +-----END PRIVATE KEY-----