Skip to content

Commit

Permalink
[Bitcoin]: Add real P2TR transaction tests
Browse files Browse the repository at this point in the history
  • Loading branch information
satoshiotomakan committed Jul 11, 2024
1 parent 169f6c7 commit d7cf113
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 58 deletions.
107 changes: 107 additions & 0 deletions rust/tw_any_coin/tests/chains/bitcoin/bitcoin_sign/p2tr_key_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,110 @@ fn test_bitcoin_sign_input_p2tr_address() {
fn test_bitcoin_sign_input_p2tr_custom_script() {
test_bitcoin_sign_output_p2tr(P2TRClaimingScriptType::P2TRCustomScript);
}

#[test]
fn test_bitcoin_sign_input_p2tr_key_path_with_change_output_a9c63d() {
const PRIVATE_KEY: &str = "7fa638b0df495b2968ae6dc7011c4db08c86df16c91aa71a77ee6a222954e5bb";
const SEND_TO: &str = "bc1qtaquch7d90x37qre6f75z5a6l0luzh0c03epyz";

let my_private_key = schnorr::PrivateKey::try_from(PRIVATE_KEY).unwrap();
let my_public_key = my_private_key.public();

let txid = "75ed78f0ae2bad924065d2357ef01184ceee2181c44e03337746512be9371a82";
let utxo0 = Proto::Input {
out_point: input::out_point(txid, 1),
value: 8_802,
sighash_type: SIGHASH_ALL,
claiming_script: input::p2tr_key_path(my_public_key.to_vec()),
..Default::default()
};

let out0 = Proto::Output {
value: 3_000,
to_recipient: output::to_address(SEND_TO),
};
// Send the change amount back to the same P2TR address.
let change_output = Proto::Output {
to_recipient: output::p2tr_key_path(my_public_key.to_vec()),
..Proto::Output::default()
};

let signing = Proto::SigningInput {
version: Proto::TransactionVersion::V2,
private_keys: vec![PRIVATE_KEY.decode_hex().unwrap().into()],
inputs: vec![utxo0],
outputs: vec![out0],
input_selector: Proto::InputSelector::SelectDescending,
fee_per_vb: 8,
change_output: Some(change_output),
chain_info: btc_info(),
dangerous_use_fixed_schnorr_rng: true,
dust_policy: dust_threshold(DUST),
..Default::default()
};

// Successfully broadcasted: https://mempool.space/tx/a9c63dfe54f6ff462155d966a54226c456b3e43b52a9abe55d7fa87d6564c6e4
let txid = "a9c63dfe54f6ff462155d966a54226c456b3e43b52a9abe55d7fa87d6564c6e4";
sign::BitcoinSignHelper::new(&signing)
.coin(CoinType::Bitcoin)
.sign(sign::Expected {
encoded: "02000000000101821a37e92b51467733034ec48121eece8411f07e35d2654092ad2baef078ed750100000000ffffffff02b80b0000000000001600145f41cc5fcd2bcd1f0079d27d4153bafbffc15df83212000000000000225120412a773e0bba5cfb5462d024cd4bf2cce1b8688a9e7a7a3f8507ebba8f00de580140cbe4d13bc9e067b042179e2c217e4e4b1d552119d12839aa4df11c21282f9159e2c4b58a4f22b291c200c0d0c5f277902282bdd78589dff0edbea89d3f00d77400000000",
txid,
inputs: vec![8_802],
outputs: vec![3_000, 4_658],
vsize: 142,
weight: 568,
fee: 1_144,
});
}

#[test]
fn test_bitcoin_sign_input_p2tr_key_path_with_max_amount_89c5d1() {
const PRIVATE_KEY: &str = "7fa638b0df495b2968ae6dc7011c4db08c86df16c91aa71a77ee6a222954e5bb";
const SEND_TO: &str = "bc1qtaquch7d90x37qre6f75z5a6l0luzh0c03epyz";

let my_private_key = schnorr::PrivateKey::try_from(PRIVATE_KEY).unwrap();
let my_public_key = my_private_key.public();

let txid = "a9c63dfe54f6ff462155d966a54226c456b3e43b52a9abe55d7fa87d6564c6e4";
let utxo0 = Proto::Input {
out_point: input::out_point(txid, 1),
value: 4_658,
sighash_type: SIGHASH_ALL,
claiming_script: input::p2tr_key_path(my_public_key.to_vec()),
..Default::default()
};

// Send max amount to an address.
let max_output = Proto::Output {
to_recipient: output::to_address(SEND_TO),
..Proto::Output::default()
};

let signing = Proto::SigningInput {
version: Proto::TransactionVersion::V2,
private_keys: vec![PRIVATE_KEY.decode_hex().unwrap().into()],
inputs: vec![utxo0],
input_selector: Proto::InputSelector::SelectDescending,
fee_per_vb: 6,
max_amount_output: Some(max_output),
chain_info: btc_info(),
dangerous_use_fixed_schnorr_rng: true,
dust_policy: dust_threshold(DUST),
..Default::default()
};

// Successfully broadcasted: https://mempool.space/tx/89c5d1d6242677a409c20fee95d6c7f7f397b82f707c96cca8e83308c4400f07
let txid = "89c5d1d6242677a409c20fee95d6c7f7f397b82f707c96cca8e83308c4400f07";
sign::BitcoinSignHelper::new(&signing)
.coin(CoinType::Bitcoin)
.sign(sign::Expected {
encoded: "02000000000101e4c664657da87f5de5aba9523be4b356c42642a566d9552146fff654fe3dc6a90100000000ffffffff01da0f0000000000001600145f41cc5fcd2bcd1f0079d27d4153bafbffc15df80140f480cb921aeae4ab03e0455da56517b6a9f2f90145e4432e0c1c39b1c06a42aa8265b30bed2d4185de2302faa89cb2a664a8a38b7fe9808a926263b7bf7cffb800000000",
txid,
inputs: vec![4_658],
outputs: vec![4_058],
vsize: 99,
weight: 396,
fee: 600,
});
}
4 changes: 2 additions & 2 deletions rust/tw_any_coin/tests/chains/common/bitcoin/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ impl<'a> BitcoinSignHelper<'a> {
// Collect all the Output amounts.
let output_outputs: Vec<_> = tx.outputs.iter().map(|output| output.value).collect();

assert_eq!(output_inputs, expected.inputs, "Wrong UTXOs");
assert_eq!(output_outputs, expected.outputs, "Wrong Outputs");
assert_eq!(
output.encoded.to_hex(),
expected.encoded,
"Wrong encoded signed transaction"
);
assert_eq!(output_inputs, expected.inputs, "Wrong UTXOs");
assert_eq!(output_outputs, expected.outputs, "Wrong Outputs");
assert_eq!(output.txid.to_hex(), expected.txid, "Wrong txid");
assert_eq!(output.vsize, expected.vsize, "Wrong vsize");
assert_eq!(output.weight, expected.weight, "Wrong weight");
Expand Down
11 changes: 6 additions & 5 deletions src/proto/BitcoinV2.proto
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ message OutPoint {
message Input {
// Reference to the previous transaction's output.
OutPoint out_point = 1;
// The amount of satoshis of this input. Required for producing Segwit/Taproot transactions.
// The amount of satoshis of this input.
int64 value = 2;
// The sighash type, normally `All`.
// See `TWBitcoinSigHashType` enum.
Expand Down Expand Up @@ -214,16 +214,17 @@ message SigningInput {
repeated Output outputs = 6;
// How the inputs should be selected.
InputSelector input_selector = 7;
// (optional) The amount of satoshis per vbyte ("satVb"), used for fee calculation.
// The amount of satoshis per vbyte ("satVb"), used for fee calculation.
// Can be satoshis per byte ("satB") **ONLY** when transaction does not contain segwit UTXOs.
int64 fee_per_vb = 8;
// The change output to be added (return to sender) at the end of the outputs list.
// The `Output.value` will be overwritten, leave as 0.
// (optional) The change output to be added (return to sender) at the end of the outputs list.
// The `Output.value` will be overwritten, leave default.
// Note there can be no change output if the change amount is less than dust threshold.
// Leave empty to explicitly disable change output creation.
Output change_output = 9;
// The only output with a max available amount to be send.
// If set, `SigningInput.outputs` and `SigningInput.change` will be ignored.
// The `Output.value` will be overwritten, leave as 0.
// The `Output.value` will be overwritten, leave default.
Output max_amount_output = 10;
// Chain info includes p2pkh, p2sh address prefixes.
// The parameter needs to be set if an input/output has a receiver address pattern.
Expand Down
102 changes: 51 additions & 51 deletions wasm/tests/Blockchain/Bitcoin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,32 +29,32 @@ describe("Bitcoin", () => {

// Now spend just created `7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca` reveal output.
const utxo0 = Proto.Input.create({
outPoint: Proto.OutPoint.create({
outPoint: {
hash: txIdInscription,
vout: 0,
}),
},
value: dustSatoshis,
sighashType: BitcoinSigHashType.all.value,
scriptBuilder: Proto.Input.InputBuilder.create({
p2wpkh: Proto.PublicKeyOrHash.create({
pubkey: publicKey.data()
})
})
scriptBuilder: {
p2wpkh: {
pubkey: publicKey.data(),
},
},
});

// UTXO to cover fee.
const utxo1 = Proto.Input.create({
outPoint: Proto.OutPoint.create({
outPoint: {
hash: txIdForFees,
vout: 1,
}),
},
value: new Long(16_400),
sighashType: BitcoinSigHashType.all.value,
scriptBuilder: Proto.Input.InputBuilder.create({
p2wpkh: Proto.PublicKeyOrHash.create({
pubkey: publicKey.data()
})
})
scriptBuilder: {
p2wpkh: {
pubkey: publicKey.data(),
},
},
});

const out0 = Proto.Output.create({
Expand All @@ -65,11 +65,11 @@ describe("Bitcoin", () => {
// Change/return transaction. Set it explicitly.
const changeOut = Proto.Output.create({
value: new Long(13_400),
builder: Proto.Output.OutputBuilder.create({
p2wpkh: Proto.PublicKeyOrHash.create({
builder: {
p2wpkh: {
pubkey: publicKey.data()
})
})
},
},
});

const signingInput = Proto.SigningInput.create({
Expand All @@ -78,10 +78,10 @@ describe("Bitcoin", () => {
inputs: [utxo0, utxo1],
outputs: [out0, changeOut],
inputSelector: Proto.InputSelector.UseAll,
chainInfo: Proto.ChainInfo.create({
chainInfo: {
p2pkhPrefix: 0,
p2shPrefix: 5,
}),
},
fixedDustThreshold: dustSatoshis,
});

Expand Down Expand Up @@ -118,38 +118,38 @@ describe("Bitcoin", () => {
const publicKey = privateKey.getPublicKeySecp256k1(true);

const utxo0 = Proto.Input.create({
outPoint: Proto.OutPoint.create({
outPoint: {
hash: txId,
vout: 1,
}),
},
value: new Long(26_400),
sighashType: BitcoinSigHashType.all.value,
scriptBuilder: Proto.Input.InputBuilder.create({
p2wpkh: Proto.PublicKeyOrHash.create({
pubkey: publicKey.data()
})
})
scriptBuilder: {
p2wpkh: {
pubkey: publicKey.data(),
},
},
});

const out0 = Proto.Output.create({
value: new Long(7_000),
builder: Proto.Output.OutputBuilder.create({
brc20Inscribe: Proto.Output.OutputBrc20Inscription.create({
builder: {
brc20Inscribe: {
inscribeTo: publicKey.data(),
ticker: "oadf",
transferAmount: "20",
})
})
},
},
});

// Change/return transaction. Set it explicitly.
const changeOut = Proto.Output.create({
value: new Long(16_400),
builder: Proto.Output.OutputBuilder.create({
p2wpkh: Proto.PublicKeyOrHash.create({
pubkey: publicKey.data()
})
})
builder: {
p2wpkh: {
pubkey: publicKey.data(),
},
},
});

const signingInput = Proto.SigningInput.create({
Expand All @@ -158,10 +158,10 @@ describe("Bitcoin", () => {
inputs: [utxo0],
outputs: [out0, changeOut],
inputSelector: Proto.InputSelector.UseAll,
chainInfo: Proto.ChainInfo.create({
chainInfo: {
p2pkhPrefix: 0,
p2shPrefix: 5,
}),
},
fixedDustThreshold: dustAmount,
});

Expand Down Expand Up @@ -199,28 +199,28 @@ describe("Bitcoin", () => {

// Now spend just created `797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1` commit output.
const utxo0 = Proto.Input.create({
outPoint: Proto.OutPoint.create({
outPoint: {
hash: txIdCommit,
vout: 0,
}),
},
value: new Long(7_000),
sighashType: BitcoinSigHashType.all.value,
scriptBuilder: Proto.Input.InputBuilder.create({
brc20Inscribe: Proto.Input.InputBrc20Inscription.create({
scriptBuilder: {
brc20Inscribe: {
inscribeTo: publicKey.data(),
ticker: "oadf",
transferAmount: "20",
}),
}),
},
},
});

const out0 = Proto.Output.create({
value: dustAmount,
builder: Proto.Output.OutputBuilder.create({
p2wpkh: Proto.PublicKeyOrHash.create({
pubkey: publicKey.data()
})
})
builder: {
p2wpkh: {
pubkey: publicKey.data(),
},
},
});

const signingInput = Proto.SigningInput.create({
Expand All @@ -229,10 +229,10 @@ describe("Bitcoin", () => {
inputs: [utxo0],
outputs: [out0],
inputSelector: Proto.InputSelector.UseAll,
chainInfo: Proto.ChainInfo.create({
chainInfo: {
p2pkhPrefix: 0,
p2shPrefix: 5,
}),
},
dangerousUseFixedSchnorrRng: true,
fixedDustThreshold: dustAmount,
});
Expand Down

0 comments on commit d7cf113

Please sign in to comment.