bitcoin 0.32

This commit is contained in:
Luke Parker 2024-05-21 05:27:01 -04:00
parent fb7d12ee6e
commit a0a7d63dad
No known key found for this signature in database
14 changed files with 224 additions and 168 deletions

84
Cargo.lock generated
View file

@ -862,6 +862,16 @@ dependencies = [
"tiny-keccak",
]
[[package]]
name = "base58ck"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f"
dependencies = [
"bitcoin-internals",
"bitcoin_hashes",
]
[[package]]
name = "base64"
version = "0.13.1"
@ -888,9 +898,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bech32"
version = "0.10.0-beta"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea"
checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
[[package]]
name = "beef"
@ -947,14 +957,16 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitcoin"
version = "0.31.2"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae"
checksum = "7170e7750a20974246f17ece04311b4205a6155f1db564c5b224af817663c3ea"
dependencies = [
"base58ck",
"bech32",
"bitcoin-internals",
"bitcoin-io",
"bitcoin-units",
"bitcoin_hashes",
"core2 0.3.3",
"hex-conservative",
"hex_lit",
"secp256k1",
@ -963,13 +975,19 @@ dependencies = [
[[package]]
name = "bitcoin-internals"
version = "0.2.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb"
checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2"
dependencies = [
"serde",
]
[[package]]
name = "bitcoin-io"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56"
[[package]]
name = "bitcoin-serai"
version = "0.3.0"
@ -991,13 +1009,22 @@ dependencies = [
]
[[package]]
name = "bitcoin_hashes"
version = "0.13.0"
name = "bitcoin-units"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b"
checksum = "cb54da0b28892f3c52203a7191534033e051b6f4b52bc15480681b57b7e036f5"
dependencies = [
"bitcoin-internals",
"core2 0.3.3",
"serde",
]
[[package]]
name = "bitcoin_hashes"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16"
dependencies = [
"bitcoin-io",
"hex-conservative",
"serde",
]
@ -1400,7 +1427,7 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd94671561e36e4e7de75f753f577edafb0e7c05d6e4547229fdf7938fbcd2c3"
dependencies = [
"core2 0.4.0",
"core2",
"multibase",
"multihash 0.18.1",
"serde",
@ -1584,15 +1611,6 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "core2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "239fa3ae9b63c2dc74bd3fa852d4792b8b305ae64eeede946265b6af62f1fff3"
dependencies = [
"memchr",
]
[[package]]
name = "core2"
version = "0.4.0"
@ -3128,11 +3146,11 @@ dependencies = [
[[package]]
name = "hex-conservative"
version = "0.1.1"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2"
checksum = "e1aa273bf451e37ed35ced41c71a5e2a4e29064afb104158f2514bcd71c2c986"
dependencies = [
"core2 0.3.3",
"arrayvec",
]
[[package]]
@ -3288,7 +3306,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite 0.2.14",
"socket2 0.4.10",
"socket2 0.5.6",
"tokio",
"tower-service",
"tracing",
@ -4758,7 +4776,7 @@ dependencies = [
"blake2b_simd",
"blake2s_simd",
"blake3",
"core2 0.4.0",
"core2",
"digest 0.10.7",
"multihash-derive 0.8.0",
"sha2",
@ -4772,7 +4790,7 @@ version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492"
dependencies = [
"core2 0.4.0",
"core2",
"unsigned-varint",
]
@ -4785,7 +4803,7 @@ dependencies = [
"blake2b_simd",
"blake2s_simd",
"blake3",
"core2 0.4.0",
"core2",
"digest 0.10.7",
"multihash-derive 0.9.0",
"ripemd",
@ -4815,7 +4833,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "890e72cb7396cb99ed98c1246a97b243cc16394470d94e0bc8b0c2c11d84290e"
dependencies = [
"core2 0.4.0",
"core2",
"multihash 0.19.1",
"multihash-derive-impl",
]
@ -7548,9 +7566,9 @@ dependencies = [
[[package]]
name = "secp256k1"
version = "0.28.2"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10"
checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3"
dependencies = [
"bitcoin_hashes",
"rand",
@ -7560,9 +7578,9 @@ dependencies = [
[[package]]
name = "secp256k1-sys"
version = "0.9.2"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb"
checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b"
dependencies = [
"cc",
]

View file

@ -23,7 +23,7 @@ thiserror = { version = "1", default-features = false, optional = true }
zeroize = { version = "^1.5", default-features = false }
rand_core = { version = "0.6", default-features = false }
bitcoin = { version = "0.31", default-features = false, features = ["no-std"] }
bitcoin = { version = "0.32", default-features = false }
k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits"] }
@ -36,7 +36,7 @@ serde_json = { version = "1", default-features = false, optional = true }
simple-request = { path = "../../common/request", version = "0.1", default-features = false, features = ["tls", "basic-auth"], optional = true }
[dev-dependencies]
secp256k1 = { version = "0.28", default-features = false, features = ["std"] }
secp256k1 = { version = "0.29", default-features = false, features = ["std"] }
frost = { package = "modular-frost", path = "../../crypto/frost", features = ["tests"] }

View file

@ -195,13 +195,13 @@ impl Rpc {
// If this was already successfully published, consider this having succeeded
if let RpcError::RequestError(Error { code, .. }) = e {
if code == RPC_VERIFY_ALREADY_IN_CHAIN {
return Ok(tx.txid());
return Ok(tx.compute_txid());
}
}
Err(e)?
}
};
if txid != tx.txid() {
if txid != tx.compute_txid() {
Err(RpcError::InvalidResponse("returned TX ID inequals calculated TX ID"))?;
}
Ok(txid)
@ -215,7 +215,7 @@ impl Rpc {
let tx: Transaction = encode::deserialize(&bytes)
.map_err(|_| RpcError::InvalidResponse("node sent an improperly serialized transaction"))?;
let mut tx_hash = *tx.txid().as_raw_hash().as_byte_array();
let mut tx_hash = *tx.compute_txid().as_raw_hash().as_byte_array();
tx_hash.reverse();
if hash != &tx_hash {
Err(RpcError::InvalidResponse("node replied with a different transaction"))?;

View file

@ -39,7 +39,7 @@ fn test_algorithm() {
.verify_schnorr(
&Signature::from_slice(&sig)
.expect("couldn't convert produced signature to secp256k1::Signature"),
&Message::from(Hash::hash(MESSAGE)),
&Message::from_digest_slice(Hash::hash(MESSAGE).as_ref()).unwrap(),
&x_only(&keys[&Participant::new(1).unwrap()].group_key()),
)
.unwrap()

View file

@ -4,7 +4,7 @@ use std_shims::{
io::{self, Write},
};
#[cfg(feature = "std")]
use std_shims::io::Read;
use std::io::{Read, BufReader};
use k256::{
elliptic_curve::sec1::{Tag, ToEncodedPoint},
@ -18,8 +18,8 @@ use frost::{
};
use bitcoin::{
consensus::encode::serialize, key::TweakedPublicKey, address::Payload, OutPoint, ScriptBuf,
TxOut, Transaction, Block,
consensus::encode::serialize, key::TweakedPublicKey, OutPoint, ScriptBuf, TxOut, Transaction,
Block,
};
#[cfg(feature = "std")]
use bitcoin::consensus::encode::Decodable;
@ -46,12 +46,12 @@ pub fn tweak_keys(keys: &ThresholdKeys<Secp256k1>) -> ThresholdKeys<Secp256k1> {
/// Return the Taproot address payload for a public key.
///
/// If the key is odd, this will return None.
pub fn address_payload(key: ProjectivePoint) -> Option<Payload> {
pub fn p2tr_script_buf(key: ProjectivePoint) -> Option<ScriptBuf> {
if key.to_encoded_point(true).tag() != Tag::CompressedEvenY {
return None;
}
Some(Payload::p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(x_only(&key))))
Some(ScriptBuf::new_p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(x_only(&key))))
}
/// A spendable output.
@ -89,11 +89,17 @@ impl ReceivedOutput {
/// Read a ReceivedOutput from a generic satisfying Read.
#[cfg(feature = "std")]
pub fn read<R: Read>(r: &mut R) -> io::Result<ReceivedOutput> {
Ok(ReceivedOutput {
offset: Secp256k1::read_F(r)?,
output: TxOut::consensus_decode(r).map_err(|_| io::Error::other("invalid TxOut"))?,
outpoint: OutPoint::consensus_decode(r).map_err(|_| io::Error::other("invalid OutPoint"))?,
})
let offset = Secp256k1::read_F(r)?;
let output;
let outpoint;
{
let mut buf_r = BufReader::new(r);
output =
TxOut::consensus_decode(&mut buf_r).map_err(|_| io::Error::other("invalid TxOut"))?;
outpoint =
OutPoint::consensus_decode(&mut buf_r).map_err(|_| io::Error::other("invalid OutPoint"))?;
}
Ok(ReceivedOutput { offset, output, outpoint })
}
/// Write a ReceivedOutput to a generic satisfying Write.
@ -124,7 +130,7 @@ impl Scanner {
/// Returns None if this key can't be scanned for.
pub fn new(key: ProjectivePoint) -> Option<Scanner> {
let mut scripts = HashMap::new();
scripts.insert(address_payload(key)?.script_pubkey(), Scalar::ZERO);
scripts.insert(p2tr_script_buf(key)?, Scalar::ZERO);
Some(Scanner { key, scripts })
}
@ -141,9 +147,8 @@ impl Scanner {
// chance of being even
// That means this should terminate within a very small amount of iterations
loop {
match address_payload(self.key + (ProjectivePoint::GENERATOR * offset)) {
Some(address) => {
let script = address.script_pubkey();
match p2tr_script_buf(self.key + (ProjectivePoint::GENERATOR * offset)) {
Some(script) => {
if self.scripts.contains_key(&script) {
None?;
}
@ -166,7 +171,7 @@ impl Scanner {
res.push(ReceivedOutput {
offset: *offset,
output: output.clone(),
outpoint: OutPoint::new(tx.txid(), vout),
outpoint: OutPoint::new(tx.compute_txid(), vout),
});
}
}

View file

@ -23,7 +23,7 @@ use bitcoin::{
use crate::{
crypto::Schnorr,
wallet::{ReceivedOutput, address_payload},
wallet::{ReceivedOutput, p2tr_script_buf},
};
#[rustfmt::skip]
@ -248,7 +248,7 @@ impl SignableTransaction {
/// Returns the TX ID of the transaction this will create.
pub fn txid(&self) -> [u8; 32] {
let mut res = self.tx.txid().to_byte_array();
let mut res = self.tx.compute_txid().to_byte_array();
res.reverse();
res
}
@ -288,7 +288,7 @@ impl SignableTransaction {
transcript.append_message(b"signing_input", u32::try_from(i).unwrap().to_le_bytes());
let offset = keys.clone().offset(self.offsets[i]);
if address_payload(offset.group_key())?.script_pubkey() != self.prevouts[i].script_pubkey {
if p2tr_script_buf(offset.group_key())? != self.prevouts[i].script_pubkey {
None?;
}

View file

@ -22,11 +22,10 @@ use bitcoin_serai::{
hashes::Hash as HashTrait,
blockdata::opcodes::all::OP_RETURN,
script::{PushBytesBuf, Instruction, Instructions, Script},
address::NetworkChecked,
OutPoint, Amount, TxOut, Transaction, Network, Address,
},
wallet::{
tweak_keys, address_payload, ReceivedOutput, Scanner, TransactionError, SignableTransaction,
tweak_keys, p2tr_script_buf, ReceivedOutput, Scanner, TransactionError, SignableTransaction,
},
rpc::Rpc,
};
@ -48,7 +47,7 @@ async fn send_and_get_output(rpc: &Rpc, scanner: &Scanner, key: ProjectivePoint)
"generatetoaddress",
serde_json::json!([
1,
Address::<NetworkChecked>::new(Network::Regtest, address_payload(key).unwrap())
Address::from_script(&p2tr_script_buf(key).unwrap(), Network::Regtest).unwrap()
]),
)
.await
@ -69,7 +68,7 @@ async fn send_and_get_output(rpc: &Rpc, scanner: &Scanner, key: ProjectivePoint)
assert_eq!(outputs, scanner.scan_transaction(&block.txdata[0]));
assert_eq!(outputs.len(), 1);
assert_eq!(outputs[0].outpoint(), &OutPoint::new(block.txdata[0].txid(), 0));
assert_eq!(outputs[0].outpoint(), &OutPoint::new(block.txdata[0].compute_txid(), 0));
assert_eq!(outputs[0].value(), block.txdata[0].output[0].value.to_sat());
assert_eq!(
@ -193,7 +192,7 @@ async_sequential! {
assert_eq!(output.offset(), Scalar::ZERO);
let inputs = vec![output];
let addr = || Address::<NetworkChecked>::new(Network::Regtest, address_payload(key).unwrap());
let addr = || Address::from_script(&p2tr_script_buf(key).unwrap(), Network::Regtest).unwrap();
let payments = vec![(addr(), 1000)];
assert!(SignableTransaction::new(inputs.clone(), &payments, None, None, FEE).is_ok());
@ -261,14 +260,14 @@ async_sequential! {
// Declare payments, change, fee
let payments = [
(Address::<NetworkChecked>::new(Network::Regtest, address_payload(key).unwrap()), 1005),
(Address::<NetworkChecked>::new(Network::Regtest, address_payload(offset_key).unwrap()), 1007)
(Address::from_script(&p2tr_script_buf(key).unwrap(), Network::Regtest).unwrap(), 1005),
(Address::from_script(&p2tr_script_buf(offset_key).unwrap(), Network::Regtest).unwrap(), 1007)
];
let change_offset = scanner.register_offset(Scalar::random(&mut OsRng)).unwrap();
let change_key = key + (ProjectivePoint::GENERATOR * change_offset);
let change_addr =
Address::<NetworkChecked>::new(Network::Regtest, address_payload(change_key).unwrap());
Address::from_script(&p2tr_script_buf(change_key).unwrap(), Network::Regtest).unwrap();
// Create and sign the TX
let tx = SignableTransaction::new(
@ -287,7 +286,7 @@ async_sequential! {
// Ensure we can scan it
let outputs = scanner.scan_transaction(&tx);
for (o, output) in outputs.iter().enumerate() {
assert_eq!(output.outpoint(), &OutPoint::new(tx.txid(), u32::try_from(o).unwrap()));
assert_eq!(output.outpoint(), &OutPoint::new(tx.compute_txid(), u32::try_from(o).unwrap()));
assert_eq!(&ReceivedOutput::read::<&[u8]>(&mut output.serialize().as_ref()).unwrap(), output);
}
@ -320,7 +319,7 @@ async_sequential! {
// This also tests send_raw_transaction and get_transaction, which the RPC test can't
// effectively test
rpc.send_raw_transaction(&tx).await.unwrap();
let mut hash = *tx.txid().as_raw_hash().as_byte_array();
let mut hash = *tx.compute_txid().as_raw_hash().as_byte_array();
hash.reverse();
assert_eq!(tx, rpc.get_transaction(&hash).await.unwrap());
assert_eq!(expected_id, hash);
@ -344,7 +343,7 @@ async_sequential! {
&SignableTransaction::new(
vec![output],
&[],
Some(&Address::<NetworkChecked>::new(Network::Regtest, address_payload(key).unwrap())),
Some(&Address::from_script(&p2tr_script_buf(key).unwrap(), Network::Regtest).unwrap()),
Some(data.clone()),
FEE
).unwrap()

View file

@ -45,7 +45,7 @@ frost-schnorrkel = { path = "../crypto/schnorrkel", default-features = false }
k256 = { version = "^0.13.1", default-features = false, features = ["std"], optional = true }
# Bitcoin
secp256k1 = { version = "0.28", default-features = false, features = ["std", "global-context", "rand-std"], optional = true }
secp256k1 = { version = "0.29", default-features = false, features = ["std", "global-context", "rand-std"], optional = true }
bitcoin-serai = { path = "../coins/bitcoin", default-features = false, features = ["std"], optional = true }
# Ethereum

View file

@ -20,12 +20,12 @@ use bitcoin_serai::{
key::{Parity, XOnlyPublicKey},
consensus::{Encodable, Decodable},
script::Instruction,
address::{NetworkChecked, Address as BAddress},
address::Address as BAddress,
Transaction, Block, Network as BNetwork, ScriptBuf,
opcodes::all::{OP_SHA256, OP_EQUALVERIFY},
},
wallet::{
tweak_keys, address_payload, ReceivedOutput, Scanner, TransactionError,
tweak_keys, p2tr_script_buf, ReceivedOutput, Scanner, TransactionError,
SignableTransaction as BSignableTransaction, TransactionMachine,
},
rpc::{RpcError, Rpc},
@ -175,7 +175,7 @@ pub struct Fee(u64);
impl TransactionTrait<Bitcoin> for Transaction {
type Id = [u8; 32];
fn id(&self) -> Self::Id {
let mut hash = *self.txid().as_raw_hash().as_byte_array();
let mut hash = *self.compute_txid().as_raw_hash().as_byte_array();
hash.reverse();
hash
}
@ -243,7 +243,8 @@ impl EventualityTrait for Eventuality {
buf
}
fn read_completion<R: io::Read>(reader: &mut R) -> io::Result<Transaction> {
Transaction::consensus_decode(reader).map_err(|e| io::Error::other(format!("{e}")))
Transaction::consensus_decode(&mut io::BufReader::new(reader))
.map_err(|e| io::Error::other(format!("{e}")))
}
}
@ -535,11 +536,11 @@ impl Bitcoin {
private_key: &PrivateKey,
) -> ScriptBuf {
let public_key = PublicKey::from_private_key(SECP256K1, private_key);
let main_addr = BAddress::p2pkh(&public_key, BNetwork::Regtest);
let main_addr = BAddress::p2pkh(public_key, BNetwork::Regtest);
let mut der = SECP256K1
.sign_ecdsa_low_r(
&Message::from(
&Message::from_digest_slice(
SighashCache::new(tx)
.legacy_signature_hash(
input_index,
@ -547,8 +548,10 @@ impl Bitcoin {
EcdsaSighashType::All.to_u32(),
)
.unwrap()
.to_raw_hash(),
),
.to_raw_hash()
.as_ref(),
)
.unwrap(),
&private_key.inner,
)
.serialize_der()
@ -577,8 +580,14 @@ const MAX_INPUTS: usize = 520;
const MAX_OUTPUTS: usize = 520;
fn address_from_key(key: ProjectivePoint) -> Address {
Address::new(BAddress::<NetworkChecked>::new(BNetwork::Bitcoin, address_payload(key).unwrap()))
.unwrap()
Address::new(
BAddress::from_script(
&p2tr_script_buf(key).expect("creating address from key which isn't properly tweaked"),
BNetwork::Bitcoin,
)
.expect("couldn't go from p2tr script buf to address"),
)
.expect("couldn't create Serai-representable address for bitcoin address")
}
#[async_trait]
@ -858,7 +867,7 @@ impl Network for Bitcoin {
Err(RpcError::ConnectionError) => Err(NetworkError::ConnectionError)?,
// TODO: Distinguish already in pool vs double spend (other signing attempt succeeded) vs
// invalid transaction
Err(e) => panic!("failed to publish TX {}: {e}", tx.txid()),
Err(e) => panic!("failed to publish TX {}: {e}", tx.compute_txid()),
}
Ok(())
}
@ -909,7 +918,7 @@ impl Network for Bitcoin {
let secret_key = SecretKey::new(&mut rand_core::OsRng);
let private_key = PrivateKey::new(secret_key, BNetwork::Regtest);
let public_key = PublicKey::from_private_key(SECP256K1, &private_key);
let main_addr = BAddress::p2pkh(&public_key, BNetwork::Regtest);
let main_addr = BAddress::p2pkh(public_key, BNetwork::Regtest);
let new_block = self.get_latest_block_number().await.unwrap() + 1;
self
@ -923,7 +932,7 @@ impl Network for Bitcoin {
version: Version(2),
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid: tx.txid(), vout: 0 },
previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
script_sig: Script::new().into(),
sequence: Sequence(u32::MAX),
witness: Witness::default(),

View file

@ -70,7 +70,7 @@ mod bitcoin {
// btc key pair to send from
let private_key = PrivateKey::new(SecretKey::new(&mut rand_core::OsRng), BNetwork::Regtest);
let public_key = PublicKey::from_private_key(SECP256K1, &private_key);
let main_addr = BAddress::p2pkh(&public_key, BNetwork::Regtest);
let main_addr = BAddress::p2pkh(public_key, BNetwork::Regtest);
// get unlocked coins
let new_block = btc.get_latest_block_number().await.unwrap() + 1;
@ -107,7 +107,7 @@ mod bitcoin {
version: Version(2),
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid: tx.txid(), vout: 0 },
previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
script_sig: Script::new().into(),
sequence: Sequence(u32::MAX),
witness: Witness::default(),
@ -128,7 +128,7 @@ mod bitcoin {
version: Version(2),
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid: tx.txid(), vout: 0 },
previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
script_sig: Script::new().into(),
sequence: Sequence(u32::MAX),
witness: Witness::new(),
@ -143,12 +143,14 @@ mod bitcoin {
// This is the standard script with an extra argument of the InInstruction
let mut sig = SECP256K1
.sign_ecdsa_low_r(
&Message::from(
&Message::from_digest_slice(
SighashCache::new(&tx)
.p2wsh_signature_hash(0, &script, initial_output_value, EcdsaSighashType::All)
.unwrap()
.to_raw_hash(),
),
.to_raw_hash()
.as_ref(),
)
.unwrap(),
&private_key.inner,
)
.serialize_der()

View file

@ -36,7 +36,7 @@ async-lock = "3"
simple-request = { path = "../../common/request", version = "0.1", optional = true }
bitcoin = { version = "0.31", optional = true }
bitcoin = { version = "0.32", 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

@ -6,8 +6,8 @@ use bitcoin::{
hashes::{Hash as HashTrait, hash160::Hash},
PubkeyHash, ScriptHash,
network::Network,
WitnessVersion, WitnessProgram,
address::{Error, Payload, NetworkChecked, Address as BAddressGeneric},
WitnessVersion, WitnessProgram, ScriptBuf,
address::{AddressType, NetworkChecked, Address as BAddressGeneric},
};
type BAddress = BAddressGeneric<NetworkChecked>;
@ -17,21 +17,22 @@ pub struct Address(BAddress);
impl PartialEq for Address {
fn eq(&self, other: &Self) -> bool {
// 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()
// Since Serai defines the Bitcoin-address specification as a variant of the script alone,
// define equivalency as the script alone
self.0.script_pubkey() == other.0.script_pubkey()
}
}
impl FromStr for Address {
type Err = Error;
fn from_str(str: &str) -> Result<Address, Error> {
type Err = ();
fn from_str(str: &str) -> Result<Address, ()> {
Address::new(
BAddressGeneric::from_str(str)
.map_err(|_| Error::UnrecognizedScript)?
.require_network(Network::Bitcoin)?,
.map_err(|_| ())?
.require_network(Network::Bitcoin)
.map_err(|_| ())?,
)
.ok_or(Error::UnrecognizedScript)
.ok_or(())
}
}
@ -54,55 +55,65 @@ enum EncodedAddress {
impl TryFrom<Vec<u8>> for Address {
type Error = ();
fn try_from(data: Vec<u8>) -> Result<Address, ()> {
Ok(Address(BAddress::new(
Network::Bitcoin,
match EncodedAddress::decode(&mut data.as_ref()).map_err(|_| ())? {
EncodedAddress::P2PKH(hash) => {
Payload::PubkeyHash(PubkeyHash::from_raw_hash(Hash::from_byte_array(hash)))
}
EncodedAddress::P2SH(hash) => {
Payload::ScriptHash(ScriptHash::from_raw_hash(Hash::from_byte_array(hash)))
}
EncodedAddress::P2WPKH(hash) => {
Payload::WitnessProgram(WitnessProgram::new(WitnessVersion::V0, hash).unwrap())
}
EncodedAddress::P2WSH(hash) => {
Payload::WitnessProgram(WitnessProgram::new(WitnessVersion::V0, hash).unwrap())
}
EncodedAddress::P2TR(key) => {
Payload::WitnessProgram(WitnessProgram::new(WitnessVersion::V1, key).unwrap())
}
},
)))
Ok(Address(match EncodedAddress::decode(&mut data.as_ref()).map_err(|_| ())? {
EncodedAddress::P2PKH(hash) => {
BAddress::p2pkh(PubkeyHash::from_raw_hash(Hash::from_byte_array(hash)), Network::Bitcoin)
}
EncodedAddress::P2SH(hash) => {
let script_hash = ScriptHash::from_raw_hash(Hash::from_byte_array(hash));
let res =
BAddress::from_script(&ScriptBuf::new_p2sh(&script_hash), Network::Bitcoin).unwrap();
debug_assert_eq!(res.script_hash(), Some(script_hash));
res
}
EncodedAddress::P2WPKH(hash) => BAddress::from_witness_program(
WitnessProgram::new(WitnessVersion::V0, &hash).unwrap(),
Network::Bitcoin,
),
EncodedAddress::P2WSH(hash) => BAddress::from_witness_program(
WitnessProgram::new(WitnessVersion::V0, &hash).unwrap(),
Network::Bitcoin,
),
EncodedAddress::P2TR(key) => BAddress::from_witness_program(
WitnessProgram::new(WitnessVersion::V1, &key).unwrap(),
Network::Bitcoin,
),
}))
}
}
fn try_to_vec(addr: &Address) -> Result<Vec<u8>, ()> {
let witness_program = |addr: &Address| {
let script = addr.0.script_pubkey();
let program_push = script.as_script().instructions().last().ok_or(())?.map_err(|_| ())?;
let program = program_push.push_bytes().ok_or(())?.as_bytes();
Ok::<_, ()>(program.to_vec())
};
Ok(
(match addr.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() {
WitnessVersion::V0 => {
let program = program.program();
if program.len() == 20 {
let mut buf = [0; 20];
buf.copy_from_slice(program.as_ref());
EncodedAddress::P2WPKH(buf)
} else if program.len() == 32 {
let mut buf = [0; 32];
buf.copy_from_slice(program.as_ref());
EncodedAddress::P2WSH(buf)
} else {
Err(())?
}
}
WitnessVersion::V1 => {
let program_ref: &[u8] = program.program().as_ref();
EncodedAddress::P2TR(program_ref.try_into().map_err(|_| ())?)
}
_ => Err(())?,
},
(match addr.0.address_type() {
Some(AddressType::P2pkh) => {
EncodedAddress::P2PKH(*addr.0.pubkey_hash().unwrap().as_raw_hash().as_byte_array())
}
Some(AddressType::P2sh) => {
EncodedAddress::P2SH(*addr.0.script_hash().unwrap().as_raw_hash().as_byte_array())
}
Some(AddressType::P2wpkh) => {
let program = witness_program(addr)?;
let mut buf = [0; 20];
buf.copy_from_slice(program.as_ref());
EncodedAddress::P2WPKH(buf)
}
Some(AddressType::P2wsh) => {
let program = witness_program(addr)?;
let mut buf = [0; 32];
buf.copy_from_slice(program.as_ref());
EncodedAddress::P2WSH(buf)
}
Some(AddressType::P2tr) => {
let program = witness_program(addr)?;
let program_ref: &[u8] = program.as_ref();
EncodedAddress::P2TR(program_ref.try_into().map_err(|_| ())?)
}
_ => Err(())?,
})
.encode(),

View file

@ -57,7 +57,7 @@ async fn mint_and_burn_test() {
};
let addr = Address::p2pkh(
&PublicKey::from_private_key(
PublicKey::from_private_key(
SECP256K1,
&PrivateKey::new(SecretKey::from_slice(&[0x01; 32]).unwrap(), Network::Bitcoin),
),
@ -266,14 +266,13 @@ async fn mint_and_burn_test() {
script::{PushBytesBuf, Script, ScriptBuf, Builder},
absolute::LockTime,
transaction::{Version, Transaction},
address::Payload,
Sequence, Witness, OutPoint, TxIn, Amount, TxOut, Network,
Sequence, Witness, OutPoint, TxIn, Amount, TxOut, Network, Address,
};
let private_key =
PrivateKey::new(SecretKey::from_slice(&[0x01; 32]).unwrap(), Network::Bitcoin);
let public_key = PublicKey::from_private_key(SECP256K1, &private_key);
let addr = Payload::p2pkh(&public_key);
let addr = Address::p2pkh(public_key, Network::Bitcoin);
// Use the first block's coinbase
let rpc = handles[0].bitcoin(&ops).await;
@ -284,7 +283,7 @@ async fn mint_and_burn_test() {
version: Version(2),
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid: tx.txid(), vout: 0 },
previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
script_sig: Script::new().into(),
sequence: Sequence(u32::MAX),
witness: Witness::default(),
@ -292,17 +291,23 @@ async fn mint_and_burn_test() {
output: vec![
TxOut {
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(),
))
script_pubkey: Address::p2tr_tweaked(
TweakedPublicKey::dangerous_assume_tweaked(
XOnlyPublicKey::from_slice(&bitcoin_key_pair.1[1 ..]).unwrap(),
),
Network::Bitcoin,
)
.script_pubkey(),
},
TxOut {
// change = amount spent - fee
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: Address::p2tr_tweaked(
TweakedPublicKey::dangerous_assume_tweaked(
XOnlyPublicKey::from_slice(&public_key.inner.serialize()[1 ..]).unwrap(),
),
Network::Bitcoin,
)
.script_pubkey(),
},
TxOut {
@ -316,12 +321,14 @@ async fn mint_and_burn_test() {
let mut der = SECP256K1
.sign_ecdsa_low_r(
&Message::from(
&Message::from_digest_slice(
SighashCache::new(&tx)
.legacy_signature_hash(0, &addr.script_pubkey(), EcdsaSighashType::All.to_u32())
.unwrap()
.to_raw_hash(),
),
.to_raw_hash()
.as_ref(),
)
.unwrap(),
&private_key.inner,
)
.serialize_der()
@ -449,9 +456,9 @@ async fn mint_and_burn_test() {
let bitcoin_addr = {
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
// TODO: Move Serai to ScriptBuf from Address
Address::p2pkh(
&loop {
loop {
let mut bytes = [0; 33];
OsRng.fill_bytes(&mut bytes);
bytes[0] %= 4;

View file

@ -126,7 +126,7 @@ impl Wallet {
let secret_key = SecretKey::new(&mut rand_core::OsRng);
let private_key = PrivateKey::new(secret_key, Network::Regtest);
let public_key = PublicKey::from_private_key(SECP256K1, &private_key);
let main_addr = Address::p2pkh(&public_key, Network::Regtest);
let main_addr = Address::p2pkh(public_key, Network::Regtest);
let rpc = Rpc::new(rpc_url).await.expect("couldn't connect to the Bitcoin RPC");
@ -258,10 +258,10 @@ impl Wallet {
consensus::Encodable,
sighash::{EcdsaSighashType, SighashCache},
script::{PushBytesBuf, Script, ScriptBuf, Builder},
address::Payload,
OutPoint, Sequence, Witness, TxIn, Amount, TxOut,
absolute::LockTime,
transaction::{Version, Transaction},
Network, Address,
};
const AMOUNT: u64 = 100000000;
@ -269,7 +269,7 @@ impl Wallet {
version: Version(2),
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid: input_tx.txid(), vout: 0 },
previous_output: OutPoint { txid: input_tx.compute_txid(), vout: 0 },
script_sig: Script::new().into(),
sequence: Sequence(u32::MAX),
witness: Witness::default(),
@ -281,9 +281,12 @@ impl Wallet {
},
TxOut {
value: Amount::from_sat(AMOUNT),
script_pubkey: Payload::p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(
XOnlyPublicKey::from_slice(&to[1 ..]).unwrap(),
))
script_pubkey: Address::p2tr_tweaked(
TweakedPublicKey::dangerous_assume_tweaked(
XOnlyPublicKey::from_slice(&to[1 ..]).unwrap(),
),
Network::Bitcoin,
)
.script_pubkey(),
},
],
@ -303,7 +306,7 @@ impl Wallet {
let mut der = SECP256K1
.sign_ecdsa_low_r(
&Message::from(
&Message::from_digest_slice(
SighashCache::new(&tx)
.legacy_signature_hash(
0,
@ -311,8 +314,10 @@ impl Wallet {
EcdsaSighashType::All.to_u32(),
)
.unwrap()
.to_raw_hash(),
),
.to_raw_hash()
.as_ref(),
)
.unwrap(),
&private_key.inner,
)
.serialize_der()