bitcoin 0.31

This commit is contained in:
Luke Parker 2023-10-30 04:45:20 -04:00
parent 2958f196fc
commit 34bcb9eb01
12 changed files with 122 additions and 91 deletions

50
Cargo.lock generated
View file

@ -440,6 +440,12 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"
[[package]]
name = "bech32"
version = "0.10.0-beta"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea"
[[package]]
name = "beef"
version = "0.5.2"
@ -496,24 +502,28 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitcoin"
version = "0.30.1"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e99ff7289b20a7385f66a0feda78af2fc119d28fb56aea8886a9cd0a4abdd75"
checksum = "5973a027b341b462105675962214dfe3c938ad9afd395d84b28602608bdcec7b"
dependencies = [
"bech32",
"bitcoin-private",
"bech32 0.10.0-beta",
"bitcoin-internals",
"bitcoin_hashes",
"core2 0.3.3",
"hex-conservative",
"hex_lit",
"secp256k1",
"serde",
]
[[package]]
name = "bitcoin-private"
version = "0.1.0"
name = "bitcoin-internals"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57"
checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb"
dependencies = [
"serde",
]
[[package]]
name = "bitcoin-serai"
@ -538,12 +548,13 @@ dependencies = [
[[package]]
name = "bitcoin_hashes"
version = "0.12.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501"
checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b"
dependencies = [
"bitcoin-private",
"bitcoin-internals",
"core2 0.3.3",
"hex-conservative",
"serde",
]
@ -1050,7 +1061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979"
dependencies = [
"base64 0.21.5",
"bech32",
"bech32 0.9.1",
"bs58",
"digest 0.10.7",
"generic-array 0.14.7",
@ -3274,6 +3285,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hex-conservative"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2"
dependencies = [
"core2 0.3.3",
]
[[package]]
name = "hex-literal"
version = "0.4.1"
@ -8044,9 +8064,9 @@ dependencies = [
[[package]]
name = "secp256k1"
version = "0.27.0"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f"
checksum = "2acea373acb8c21ecb5a23741452acd2593ed44ee3d343e72baaa143bc89d0d5"
dependencies = [
"bitcoin_hashes",
"rand",
@ -8056,9 +8076,9 @@ dependencies = [
[[package]]
name = "secp256k1-sys"
version = "0.8.1"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e"
checksum = "09e67c467c38fd24bd5499dc9a18183b31575c12ee549197e3e20d57aa4fe3b7"
dependencies = [
"cc",
]

View file

@ -17,8 +17,8 @@ rand_core = { version = "0.6", default-features = false }
sha2 = { version = "0.10", default-features = false }
secp256k1 = { version = "0.27", default-features = false }
bitcoin = { version = "0.30", default-features = false, features = ["no-std"] }
secp256k1 = { version = "0.28", default-features = false }
bitcoin = { version = "0.31", default-features = false, features = ["no-std"] }
k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits"] }

View file

@ -83,7 +83,7 @@ impl ReceivedOutput {
/// The value of this output.
pub fn value(&self) -> u64 {
self.output.value
self.output.value.to_sat()
}
/// Read a ReceivedOutput from a generic satisfying Read.

View file

@ -16,7 +16,8 @@ use bitcoin::{
sighash::{TapSighashType, SighashCache, Prevouts},
absolute::LockTime,
script::{PushBytesBuf, ScriptBuf},
OutPoint, Sequence, Witness, TxIn, TxOut, Transaction, Address,
transaction::{Version, Transaction},
OutPoint, Sequence, Witness, TxIn, Amount, TxOut, Address,
};
use crate::{
@ -62,7 +63,7 @@ impl SignableTransaction {
fn calculate_weight(inputs: usize, payments: &[(Address, u64)], change: Option<&Address>) -> u64 {
// Expand this a full transaction in order to use the bitcoin library's weight function
let mut tx = Transaction {
version: 2,
version: Version(2),
lock_time: LockTime::ZERO,
input: vec![
TxIn {
@ -82,13 +83,16 @@ impl SignableTransaction {
.iter()
// The payment is a fixed size so we don't have to use it here
// The script pub key is not of a fixed size and does have to be used here
.map(|payment| TxOut { value: payment.1, script_pubkey: payment.0.script_pubkey() })
.map(|payment| TxOut {
value: Amount::from_sat(payment.1),
script_pubkey: payment.0.script_pubkey(),
})
.collect(),
};
if let Some(change) = change {
// Use a 0 value since we're currently unsure what the change amount will be, and since
// the value is fixed size (so any value could be used here)
tx.output.push(TxOut { value: 0, script_pubkey: change.script_pubkey() });
tx.output.push(TxOut { value: Amount::ZERO, script_pubkey: change.script_pubkey() });
}
u64::try_from(tx.weight()).unwrap()
}
@ -103,8 +107,8 @@ impl SignableTransaction {
/// Returns the fee this transaction will use.
pub fn fee(&self) -> u64 {
self.prevouts.iter().map(|prevout| prevout.value).sum::<u64>() -
self.tx.output.iter().map(|prevout| prevout.value).sum::<u64>()
self.prevouts.iter().map(|prevout| prevout.value.to_sat()).sum::<u64>() -
self.tx.output.iter().map(|prevout| prevout.value.to_sat()).sum::<u64>()
}
/// Create a new SignableTransaction.
@ -139,7 +143,7 @@ impl SignableTransaction {
Err(TransactionError::TooMuchData)?;
}
let input_sat = inputs.iter().map(|input| input.output.value).sum::<u64>();
let input_sat = inputs.iter().map(|input| input.output.value.to_sat()).sum::<u64>();
let offsets = inputs.iter().map(|input| input.offset).collect();
let tx_ins = inputs
.iter()
@ -154,15 +158,18 @@ impl SignableTransaction {
let payment_sat = payments.iter().map(|payment| payment.1).sum::<u64>();
let mut tx_outs = payments
.iter()
.map(|payment| TxOut { value: payment.1, script_pubkey: payment.0.script_pubkey() })
.map(|payment| TxOut {
value: Amount::from_sat(payment.1),
script_pubkey: payment.0.script_pubkey(),
})
.collect::<Vec<_>>();
// Add the OP_RETURN output
if let Some(data) = data {
tx_outs.push(TxOut {
value: 0,
value: Amount::ZERO,
script_pubkey: ScriptBuf::new_op_return(
&PushBytesBuf::try_from(data)
PushBytesBuf::try_from(data)
.expect("data didn't fit into PushBytes depsite being checked"),
),
})
@ -209,7 +216,8 @@ impl SignableTransaction {
let fee_with_change = fee_per_weight * weight_with_change;
if let Some(value) = input_sat.checked_sub(payment_sat + fee_with_change) {
if value >= DUST {
tx_outs.push(TxOut { value, script_pubkey: change.script_pubkey() });
tx_outs
.push(TxOut { value: Amount::from_sat(value), script_pubkey: change.script_pubkey() });
weight = weight_with_change;
needed_fee = fee_with_change;
}
@ -225,7 +233,12 @@ impl SignableTransaction {
}
Ok(SignableTransaction {
tx: Transaction { version: 2, lock_time: LockTime::ZERO, input: tx_ins, output: tx_outs },
tx: Transaction {
version: Version(2),
lock_time: LockTime::ZERO,
input: tx_ins,
output: tx_outs,
},
offsets,
prevouts: inputs.drain(..).map(|input| input.output).collect(),
needed_fee,
@ -256,7 +269,7 @@ impl SignableTransaction {
}
for payment in &tx.output {
transcript.append_message(b"output_script", payment.script_pubkey.as_bytes());
transcript.append_message(b"output_amount", payment.value.to_le_bytes());
transcript.append_message(b"output_amount", payment.value.to_sat().to_le_bytes());
}
let mut sigs = vec![];

View file

@ -23,7 +23,7 @@ use bitcoin_serai::{
blockdata::opcodes::all::OP_RETURN,
script::{PushBytesBuf, Instruction, Instructions, Script},
address::NetworkChecked,
OutPoint, TxOut, Transaction, Network, Address,
OutPoint, Amount, TxOut, Transaction, Network, Address,
},
wallet::{
tweak_keys, address_payload, ReceivedOutput, Scanner, TransactionError, SignableTransaction,
@ -58,7 +58,7 @@ async fn send_and_get_output(rpc: &Rpc, scanner: &Scanner, key: ProjectivePoint)
rpc
.rpc_call::<Vec<String>>(
"generatetoaddress",
serde_json::json!([100, Address::p2sh(Script::empty(), Network::Regtest).unwrap()]),
serde_json::json!([100, Address::p2sh(Script::new(), Network::Regtest).unwrap()]),
)
.await
.unwrap();
@ -70,7 +70,7 @@ async fn send_and_get_output(rpc: &Rpc, scanner: &Scanner, key: ProjectivePoint)
assert_eq!(outputs.len(), 1);
assert_eq!(outputs[0].outpoint(), &OutPoint::new(block.txdata[0].txid(), 0));
assert_eq!(outputs[0].value(), block.txdata[0].output[0].value);
assert_eq!(outputs[0].value(), block.txdata[0].output[0].value.to_sat());
assert_eq!(
ReceivedOutput::read::<&[u8]>(&mut outputs[0].serialize().as_ref()).unwrap(),
@ -296,21 +296,24 @@ async_sequential! {
// Make sure the payments were properly created
for ((output, scanned), payment) in tx.output.iter().zip(outputs.iter()).zip(payments.iter()) {
assert_eq!(output, &TxOut { script_pubkey: payment.0.script_pubkey(), value: payment.1 });
assert_eq!(
output,
&TxOut { script_pubkey: payment.0.script_pubkey(), value: Amount::from_sat(payment.1) },
);
assert_eq!(scanned.value(), payment.1 );
}
// Make sure the change is correct
assert_eq!(needed_fee, u64::try_from(tx.weight()).unwrap() * FEE);
let input_value = output.value() + offset_output.value();
let output_value = tx.output.iter().map(|output| output.value).sum::<u64>();
let output_value = tx.output.iter().map(|output| output.value.to_sat()).sum::<u64>();
assert_eq!(input_value - output_value, needed_fee);
let change_amount =
input_value - payments.iter().map(|payment| payment.1).sum::<u64>() - needed_fee;
assert_eq!(
tx.output[2],
TxOut { script_pubkey: change_addr.script_pubkey(), value: change_amount },
TxOut { script_pubkey: change_addr.script_pubkey(), value: Amount::from_sat(change_amount) },
);
// This also tests send_raw_transaction and get_transaction, which the RPC test can't

View file

@ -42,7 +42,7 @@ frost-schnorrkel = { path = "../crypto/schnorrkel" }
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
# Bitcoin
secp256k1 = { version = "0.27", features = ["global-context", "rand-std"], optional = true }
secp256k1 = { version = "0.28", features = ["global-context", "rand-std"], optional = true }
k256 = { version = "^0.13.1", optional = true }
bitcoin-serai = { path = "../coins/bitcoin", optional = true }

View file

@ -35,7 +35,8 @@ use bitcoin_serai::bitcoin::{
sighash::{EcdsaSighashType, SighashCache},
script::{PushBytesBuf, Builder},
absolute::LockTime,
Sequence, Script, Witness, TxIn,
Sequence, Script, Witness, TxIn, Amount as BAmount,
transaction::Version,
};
use serai_client::{
@ -106,7 +107,7 @@ impl OutputTrait<Bitcoin> for Output {
fn key(&self) -> ProjectivePoint {
let script = &self.output.output().script_pubkey;
assert!(script.is_v1_p2tr());
assert!(script.is_p2tr());
let Instruction::PushBytes(key) = script.instructions_minimal().last().unwrap().unwrap() else {
panic!("last item in v1 Taproot script wasn't bytes")
};
@ -177,10 +178,11 @@ impl TransactionTrait<Bitcoin> for Transaction {
hash.reverse();
value += network.rpc.get_transaction(&hash).await.unwrap().output
[usize::try_from(output.vout).unwrap()]
.value;
.value
.to_sat();
}
for output in &self.output {
value -= output.value;
value -= output.value.to_sat();
}
value
}
@ -331,9 +333,10 @@ impl Bitcoin {
input_tx.reverse();
in_value += self.get_transaction(&input_tx).await?.output
[usize::try_from(input.previous_output.vout).unwrap()]
.value;
.value
.to_sat();
}
let out = tx.output.iter().map(|output| output.value).sum::<u64>();
let out = tx.output.iter().map(|output| output.value.to_sat()).sum::<u64>();
fees.push((in_value - out) / tx.weight().to_wu());
}
}
@ -708,7 +711,7 @@ impl Network for Bitcoin {
.rpc
.rpc_call::<Vec<String>>(
"generatetoaddress",
serde_json::json!([1, BAddress::p2sh(Script::empty(), BitcoinNetwork::Regtest).unwrap()]),
serde_json::json!([1, BAddress::p2sh(Script::new(), BitcoinNetwork::Regtest).unwrap()]),
)
.await
.unwrap();
@ -734,16 +737,16 @@ impl Network for Bitcoin {
let tx = self.get_block(new_block).await.unwrap().txdata.swap_remove(0);
let mut tx = Transaction {
version: 2,
version: Version(2),
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid: tx.txid(), vout: 0 },
script_sig: Script::empty().into(),
script_sig: Script::new().into(),
sequence: Sequence(u32::MAX),
witness: Witness::default(),
}],
output: vec![TxOut {
value: tx.output[0].value - 10000,
value: tx.output[0].value - BAmount::from_sat(10000),
script_pubkey: address.0.script_pubkey(),
}],
};

View file

@ -26,7 +26,7 @@ serai-runtime = { path = "../runtime", version = "0.1" }
sp-core = { git = "https://github.com/serai-dex/substrate" }
subxt = { version = "0.29", default-features = false, features = ["jsonrpsee-ws"], optional = true }
bitcoin = { version = "0.30", optional = true }
bitcoin = { version = "0.31", optional = true }
ciphersuite = { path = "../../crypto/ciphersuite", version = "0.4", optional = true }
monero-serai = { path = "../../coins/monero", version = "0.1.4-alpha", optional = true }

View file

@ -5,10 +5,9 @@ use scale::{Encode, Decode};
use bitcoin::{
hashes::{Hash as HashTrait, hash160::Hash},
PubkeyHash, ScriptHash,
network::constants::Network,
address::{
Error, WitnessVersion, Payload, WitnessProgram, NetworkChecked, Address as BAddressGeneric,
},
network::Network,
WitnessVersion, WitnessProgram,
address::{Error, Payload, NetworkChecked, Address as BAddressGeneric},
};
type BAddress = BAddressGeneric<NetworkChecked>;
@ -18,28 +17,20 @@ pub struct Address(pub BAddress);
impl PartialEq for Address {
fn eq(&self, other: &Self) -> bool {
self.0.payload == other.0.payload
// Since Serai defines the Bitcoin-address specification as a variant of the payload alone,
// define equivalency as the payload alone
self.0.payload() == other.0.payload()
}
}
impl FromStr for Address {
type Err = Error;
fn from_str(str: &str) -> Result<Address, Error> {
let mut original_address = BAddressGeneric::from_str(str)?;
// Standardize the network
original_address.network = Network::Bitcoin;
let address = original_address
.clone()
.require_network(Network::Bitcoin)
.expect("network wasn't mainnet despite overriding network");
// Also check this isn't caching the string internally
if BAddressGeneric::from_str(&address.to_string())? != original_address {
// TODO: Make this an error?
panic!("Address::from_str(address.to_string()) != address for Bitcoin");
}
Ok(Address(address))
Ok(Address(
BAddressGeneric::from_str(str)
.map_err(|_| Error::UnrecognizedScript)?
.require_network(Network::Bitcoin)?,
))
}
}
@ -90,7 +81,7 @@ impl TryInto<Vec<u8>> for Address {
type Error = ();
fn try_into(self) -> Result<Vec<u8>, ()> {
Ok(
(match self.0.payload {
(match self.0.payload() {
Payload::PubkeyHash(hash) => EncodedAddress::P2PKH(*hash.as_raw_hash().as_byte_array()),
Payload::ScriptHash(hash) => EncodedAddress::P2SH(*hash.as_raw_hash().as_byte_array()),
Payload::WitnessProgram(program) => match program.version() {

View file

@ -51,7 +51,7 @@ async fn mint_and_burn_test() {
secp256k1::{SECP256K1, SecretKey},
PrivateKey, PublicKey,
consensus::Encodable,
network::constants::Network,
network::Network,
address::Address,
};
@ -263,8 +263,9 @@ async fn mint_and_burn_test() {
sighash::{EcdsaSighashType, SighashCache},
script::{PushBytesBuf, Script, ScriptBuf, Builder},
absolute::LockTime,
transaction::{Version, Transaction},
address::Payload,
Sequence, Witness, OutPoint, TxIn, TxOut, Transaction, Network,
Sequence, Witness, OutPoint, TxIn, Amount, TxOut, Network,
};
let private_key =
@ -278,17 +279,17 @@ async fn mint_and_burn_test() {
rpc.get_block(&rpc.get_block_hash(1).await.unwrap()).await.unwrap().txdata.swap_remove(0);
#[allow(clippy::inconsistent_digit_grouping)]
let mut tx = Transaction {
version: 2,
version: Version(2),
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid: tx.txid(), vout: 0 },
script_sig: Script::empty().into(),
script_sig: Script::new().into(),
sequence: Sequence(u32::MAX),
witness: Witness::default(),
}],
output: vec![
TxOut {
value: 1_100_000_00,
value: Amount::from_sat(1_100_000_00),
script_pubkey: Payload::p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(
XOnlyPublicKey::from_slice(&bitcoin_key_pair.1[1 ..]).unwrap(),
))
@ -296,16 +297,16 @@ async fn mint_and_burn_test() {
},
TxOut {
// change = amount spent - fee
value: tx.output[0].value - 1_100_000_00 - 1_000_00,
value: Amount::from_sat(tx.output[0].value.to_sat() - 1_100_000_00 - 1_000_00),
script_pubkey: Payload::p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(
XOnlyPublicKey::from_slice(&public_key.inner.serialize()[1 ..]).unwrap(),
))
.script_pubkey(),
},
TxOut {
value: 0,
value: Amount::ZERO,
script_pubkey: ScriptBuf::new_op_return(
&PushBytesBuf::try_from(Shorthand::transfer(None, serai_addr).encode()).unwrap(),
PushBytesBuf::try_from(Shorthand::transfer(None, serai_addr).encode()).unwrap(),
),
},
],
@ -447,7 +448,7 @@ async fn mint_and_burn_test() {
// Create a random Bitcoin/Monero address
let bitcoin_addr = {
use bitcoin_serai::bitcoin::{network::constants::Network, key::PublicKey, address::Address};
use bitcoin_serai::bitcoin::{network::Network, key::PublicKey, address::Address};
// Uses Network::Bitcoin since it doesn't actually matter, Serai strips it out
// TODO: Move Serai to Payload from Address
Address::p2pkh(
@ -555,9 +556,9 @@ async fn mint_and_burn_test() {
.unwrap();
let tx_fee = 1_100_000_00 -
block.txdata[1].output.iter().map(|output| output.value).sum::<u64>();
block.txdata[1].output.iter().map(|output| output.value.to_sat()).sum::<u64>();
assert_eq!(received_output.value, 1_000_000_00 - tx_fee);
assert_eq!(received_output.value.to_sat(), 1_000_000_00 - tx_fee);
found = true;
}
} else {

View file

@ -251,7 +251,7 @@ impl Coordinator {
match self.network {
NetworkId::Bitcoin => {
use bitcoin_serai::{
bitcoin::{consensus::Encodable, network::constants::Network, Script, Address},
bitcoin::{consensus::Encodable, network::Network, Script, Address},
rpc::Rpc,
};
@ -260,7 +260,7 @@ impl Coordinator {
rpc
.rpc_call::<Vec<String>>(
"generatetoaddress",
serde_json::json!([1, Address::p2sh(Script::empty(), Network::Regtest).unwrap()]),
serde_json::json!([1, Address::p2sh(Script::new(), Network::Regtest).unwrap()]),
)
.await
.unwrap();

View file

@ -233,28 +233,28 @@ impl Wallet {
sighash::{EcdsaSighashType, SighashCache},
script::{PushBytesBuf, Script, ScriptBuf, Builder},
address::Payload,
OutPoint, Sequence, Witness, TxIn, TxOut,
OutPoint, Sequence, Witness, TxIn, Amount, TxOut,
absolute::LockTime,
Transaction,
transaction::{Version, Transaction},
};
const AMOUNT: u64 = 100000000;
let mut tx = Transaction {
version: 2,
version: Version(2),
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid: input_tx.txid(), vout: 0 },
script_sig: Script::empty().into(),
script_sig: Script::new().into(),
sequence: Sequence(u32::MAX),
witness: Witness::default(),
}],
output: vec![
TxOut {
value: input_tx.output[0].value - AMOUNT - 10000,
value: Amount::from_sat(input_tx.output[0].value.to_sat() - AMOUNT - 10000),
script_pubkey: input_tx.output[0].script_pubkey.clone(),
},
TxOut {
value: AMOUNT,
value: Amount::from_sat(AMOUNT),
script_pubkey: Payload::p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(
XOnlyPublicKey::from_slice(&to[1 ..]).unwrap(),
))
@ -265,9 +265,9 @@ impl Wallet {
if let Some(instruction) = instruction {
tx.output.push(TxOut {
value: 0,
value: Amount::ZERO,
script_pubkey: ScriptBuf::new_op_return(
&PushBytesBuf::try_from(
PushBytesBuf::try_from(
Shorthand::Raw(RefundableInInstruction { origin: None, instruction }).encode(),
)
.unwrap(),