|
32 | 32 |
|
33 | 33 | namespace TW::Bitcoin {
|
34 | 34 |
|
| 35 | +constexpr uint64_t ONE_BTC = 100'000'000; |
| 36 | + |
35 | 37 | // clang-format off
|
36 | 38 | SigningInput buildInputP2PKH(bool omitKey = false) {
|
37 | 39 | auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f");
|
@@ -574,6 +576,134 @@ TEST(BitcoinSigning, SignNftInscriptionReveal) {
|
574 | 576 | ASSERT_EQ(result.substr(292, result.size() - 292), expectedHex.substr(292, result.size() - 292));
|
575 | 577 | }
|
576 | 578 |
|
| 579 | +TEST(BitcoinSigning, PlanAndSignBrc20) { |
| 580 | + auto privateKey = parse_hex("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); |
| 581 | + auto publicKey = parse_hex("030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb"); |
| 582 | + |
| 583 | + // Construct a `BitcoinV2.Proto.ComposePlan` message. |
| 584 | + |
| 585 | + auto dustSatoshis = 546; |
| 586 | + auto ticker = "oadf"; |
| 587 | + auto tokensAmount = 20; |
| 588 | + auto feePerVb = 25; |
| 589 | + |
| 590 | + auto txId1 = parse_hex("181c84965c9ea86a5fac32fdbd5f73a21a7a9e749fb6ab97e273af2329f6b911"); |
| 591 | + std::reverse(begin(txId1), end(txId1)); |
| 592 | + |
| 593 | + BitcoinV2::Proto::Input tx1; |
| 594 | + tx1.set_txid(txId1.data(), (int)txId1.size()); |
| 595 | + tx1.set_vout(0); |
| 596 | + tx1.set_value(ONE_BTC); |
| 597 | + tx1.set_sighash_type(Utxo::Proto::SighashType::All); |
| 598 | + tx1.mutable_builder()->set_p2wpkh(publicKey.data(), (int)publicKey.size()); |
| 599 | + |
| 600 | + auto txId2 = parse_hex("858e450a1da44397bde05ca2f8a78510d74c623cc2f69736a8b3fbfadc161f6e"); |
| 601 | + std::reverse(begin(txId2), end(txId2)); |
| 602 | + |
| 603 | + BitcoinV2::Proto::Input tx2; |
| 604 | + tx2.set_txid(txId2.data(), (int)txId2.size()); |
| 605 | + tx2.set_vout(0); |
| 606 | + tx2.set_value(2 * ONE_BTC); |
| 607 | + tx2.set_sighash_type(Utxo::Proto::SighashType::All); |
| 608 | + tx2.mutable_builder()->set_p2wpkh(publicKey.data(), (int)publicKey.size()); |
| 609 | + |
| 610 | + BitcoinV2::Proto::Output taggedOutput; |
| 611 | + taggedOutput.set_value(dustSatoshis); |
| 612 | + taggedOutput.mutable_builder()->mutable_p2wpkh()->set_pubkey(publicKey.data(), (int)publicKey.size()); |
| 613 | + |
| 614 | + BitcoinV2::Proto::Output changeOutput; |
| 615 | + // Will be set by the library. |
| 616 | + changeOutput.set_value(0); |
| 617 | + changeOutput.mutable_builder()->mutable_p2wpkh()->set_pubkey(publicKey.data(), (int)publicKey.size()); |
| 618 | + |
| 619 | + BitcoinV2::Proto::Input_InputBrc20Inscription brc20Inscription; |
| 620 | + brc20Inscription.set_one_prevout(false); |
| 621 | + brc20Inscription.set_inscribe_to(publicKey.data(), (int)publicKey.size()); |
| 622 | + brc20Inscription.set_ticker(ticker); |
| 623 | + brc20Inscription.set_transfer_amount(tokensAmount); |
| 624 | + |
| 625 | + BitcoinV2::Proto::ComposePlan composePlan; |
| 626 | + auto& composeBrc20 = *composePlan.mutable_brc20(); |
| 627 | + composeBrc20.set_private_key(privateKey.data(), (int)privateKey.size()); |
| 628 | + *composeBrc20.add_inputs() = tx1; |
| 629 | + *composeBrc20.add_inputs() = tx2; |
| 630 | + composeBrc20.set_input_selector(Utxo::Proto::InputSelector::SelectAscending); |
| 631 | + *composeBrc20.mutable_tagged_output() = taggedOutput; |
| 632 | + *composeBrc20.mutable_inscription() = brc20Inscription; |
| 633 | + composeBrc20.set_fee_per_vb(feePerVb); |
| 634 | + *composeBrc20.mutable_change_output() = changeOutput; |
| 635 | + composeBrc20.set_disable_change_output(false); |
| 636 | + |
| 637 | + // Construct a `Bitcoin.Proto.SigningInput` message with `planning_v2` field only. |
| 638 | + Proto::SigningInput input; |
| 639 | + *input.mutable_planning_v2() = composePlan; |
| 640 | + |
| 641 | + // Plan the transaction using standard `TWAnySignerPlan`. |
| 642 | + Proto::TransactionPlan plan; |
| 643 | + ANY_PLAN(input, plan, TWCoinTypeBitcoin); |
| 644 | + |
| 645 | + // Check the result Planning V2. |
| 646 | + EXPECT_TRUE(plan.has_planning_result_v2()); |
| 647 | + const auto& planV2 = plan.planning_result_v2(); |
| 648 | + EXPECT_EQ(planV2.error(), BitcoinV2::Proto::Error::OK); |
| 649 | + EXPECT_TRUE(planV2.has_brc20()); |
| 650 | + const auto& brc20Plan = planV2.brc20(); |
| 651 | + |
| 652 | + // Check the result Commit `SigningInput`. |
| 653 | + auto commitOutputAmount = 3846; |
| 654 | + EXPECT_TRUE(brc20Plan.has_commit()); |
| 655 | + const auto& brc20Commit = brc20Plan.commit(); |
| 656 | + EXPECT_EQ(brc20Commit.version(), 2); |
| 657 | + EXPECT_EQ(brc20Commit.inputs_size(), 1); |
| 658 | + EXPECT_EQ(brc20Commit.outputs_size(), 2); |
| 659 | + // Change output generation is disabled, included in `commit.outputs`. |
| 660 | + EXPECT_FALSE(brc20Commit.has_change_output()); |
| 661 | + EXPECT_EQ(brc20Commit.outputs(0).value(), commitOutputAmount); |
| 662 | + EXPECT_EQ(brc20Commit.outputs(0).builder().brc20_inscribe().ticker(), ticker); |
| 663 | + EXPECT_EQ(brc20Commit.outputs(0).builder().brc20_inscribe().transfer_amount(), tokensAmount); |
| 664 | + // Change: tx1 value - out1 value |
| 665 | + EXPECT_EQ(brc20Commit.outputs(1).value(), ONE_BTC - commitOutputAmount - 3175); |
| 666 | + |
| 667 | + // Check the result Reveal `SigningInput`. |
| 668 | + EXPECT_TRUE(brc20Plan.has_reveal()); |
| 669 | + const auto& brc20Reveal = brc20Plan.reveal(); |
| 670 | + EXPECT_EQ(brc20Reveal.version(), 2); |
| 671 | + EXPECT_EQ(brc20Reveal.inputs_size(), 1); |
| 672 | + EXPECT_EQ(brc20Reveal.outputs_size(), 1); |
| 673 | + // Change output generation is disabled, included in `commit.outputs`. |
| 674 | + EXPECT_FALSE(brc20Reveal.has_change_output()); |
| 675 | + EXPECT_EQ(brc20Reveal.inputs(0).value(), commitOutputAmount); |
| 676 | + EXPECT_EQ(brc20Reveal.inputs(0).builder().brc20_inscribe().ticker(), ticker); |
| 677 | + EXPECT_EQ(brc20Reveal.inputs(0).builder().brc20_inscribe().transfer_amount(), tokensAmount); |
| 678 | + EXPECT_EQ(brc20Reveal.outputs(0).value(), dustSatoshis); |
| 679 | + |
| 680 | + // Construct a `Bitcoin.Proto.SigningInput` message with `signing_v2` (Commit) field only. |
| 681 | + { |
| 682 | + Proto::SigningInput commitInput; |
| 683 | + *commitInput.mutable_signing_v2() = brc20Commit; |
| 684 | + |
| 685 | + Proto::SigningOutput output; |
| 686 | + ANY_SIGN(commitInput, TWCoinTypeBitcoin); |
| 687 | + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); |
| 688 | + EXPECT_TRUE(output.has_signing_result_v2()); |
| 689 | + EXPECT_EQ(hex(output.signing_result_v2().encoded()), "0200000000010111b9f62923af73e297abb69f749e7a1aa2735fbdfd32ac5f6aa89e5c96841c180000000000ffffffff02060f000000000000225120e8b706a97732e705e22ae7710703e7f589ed13c636324461afa443016134cc0593c5f50500000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d02483045022100912004efb9b4e8368ba00d6bfbdfde22a43b037f64ae09d79aac030c77edbc2802206c5702646eadea2274c4aafee99c12b5054cb60da18c21f67f5f3003a318112d0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"); |
| 690 | + } |
| 691 | + |
| 692 | + // Construct a `Bitcoin.Proto.SigningInput` message with `signing_v2` (Reveal) field only. |
| 693 | + { |
| 694 | + Proto::SigningInput revealInput; |
| 695 | + *revealInput.mutable_signing_v2() = brc20Reveal; |
| 696 | + // `schnorr` is used to sign the Reveal transaction. |
| 697 | + revealInput.mutable_signing_v2()->set_dangerous_use_fixed_schnorr_rng(true); |
| 698 | + |
| 699 | + Proto::SigningOutput output; |
| 700 | + ANY_SIGN(revealInput, TWCoinTypeBitcoin); |
| 701 | + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); |
| 702 | + EXPECT_TRUE(output.has_signing_result_v2()); |
| 703 | + EXPECT_EQ(hex(output.signing_result_v2().encoded()), "0200000000010173711b50d9adb30fdc51231cd56a95b3b627453add775c56188449f2dccaef250000000000ffffffff012202000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d03405cd7fb811a8ebcc55ac791321243a6d1a4089abc548d93288dfe5870f6af7f96b0cb0c7c41c0126791179e8c190d8fecf9bdc4cc740ec3e7d6a43b1b0a345f155b0063036f7264010118746578742f706c61696e3b636861727365743d7574662d3800377b2270223a226272632d3230222c226f70223a227472616e73666572222c227469636b223a226f616466222c22616d74223a223230227d6821c00f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000"); |
| 704 | + } |
| 705 | +} |
| 706 | + |
577 | 707 | TEST(BitcoinSigning, SignP2PKH) {
|
578 | 708 | auto input = buildInputP2PKH();
|
579 | 709 |
|
|
0 commit comments