Update to bitcoin 0.30

Also performs a general update with a variety of upgraded Substrate depends.
This commit is contained in:
Luke Parker 2023-04-09 02:31:10 -04:00
parent 96525330c2
commit f6206b60ec
No known key found for this signature in database
17 changed files with 387 additions and 284 deletions

429
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -16,8 +16,8 @@ rand_core = "0.6"
sha2 = "0.10"
secp256k1 = { version = "0.24", features = ["global-context"] }
bitcoin = { version = "0.29", features = ["serde"] }
secp256k1 = { version = "0.27", features = ["global-context"] }
bitcoin = { version = "0.30", features = ["serde"] }
k256 = { version = "0.13", features = ["arithmetic"] }
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", version = "0.3", features = ["recommended"] }

View file

@ -23,7 +23,7 @@ use frost::{
algorithm::{Hram as HramTrait, Algorithm, Schnorr as FrostSchnorr},
};
use bitcoin::XOnlyPublicKey;
use bitcoin::key::XOnlyPublicKey;
/// Get the x coordinate of a non-infinity, even point. Panics on invalid input.
pub fn x(key: &ProjectivePoint) -> [u8; 32] {

View file

@ -6,10 +6,7 @@ use serde::{Deserialize, de::DeserializeOwned};
use serde_json::json;
use bitcoin::{
hashes::{
Hash,
hex::{FromHex, ToHex},
},
hashes::{Hash, hex::FromHex},
consensus::encode,
Txid, Transaction, BlockHash, Block,
};
@ -88,8 +85,11 @@ impl Rpc {
/// Get the hash of a block by the block's number.
pub async fn get_block_hash(&self, number: usize) -> Result<[u8; 32], RpcError> {
let mut hash =
self.rpc_call::<BlockHash>("getblockhash", json!([number])).await?.as_hash().into_inner();
let mut hash = *self
.rpc_call::<BlockHash>("getblockhash", json!([number]))
.await?
.as_raw_hash()
.as_byte_array();
// bitcoin stores the inner bytes in reverse order.
hash.reverse();
Ok(hash)
@ -101,16 +101,16 @@ impl Rpc {
struct Number {
height: usize,
}
Ok(self.rpc_call::<Number>("getblockheader", json!([hash.to_hex()])).await?.height)
Ok(self.rpc_call::<Number>("getblockheader", json!([hex::encode(hash)])).await?.height)
}
/// Get a block by its hash.
pub async fn get_block(&self, hash: &[u8; 32]) -> Result<Block, RpcError> {
let hex = self.rpc_call::<String>("getblock", json!([hash.to_hex(), 0])).await?;
let hex = self.rpc_call::<String>("getblock", json!([hex::encode(hash), 0])).await?;
let bytes: Vec<u8> = FromHex::from_hex(&hex).map_err(|_| RpcError::InvalidResponse)?;
let block: Block = encode::deserialize(&bytes).map_err(|_| RpcError::InvalidResponse)?;
let mut block_hash = block.block_hash().as_hash().into_inner();
let mut block_hash = *block.block_hash().as_raw_hash().as_byte_array();
block_hash.reverse();
if hash != &block_hash {
Err(RpcError::InvalidResponse)?;
@ -130,11 +130,11 @@ impl Rpc {
/// Get a transaction by its hash.
pub async fn get_transaction(&self, hash: &[u8; 32]) -> Result<Transaction, RpcError> {
let hex = self.rpc_call::<String>("getrawtransaction", json!([hash.to_hex()])).await?;
let hex = self.rpc_call::<String>("getrawtransaction", json!([hex::encode(hash)])).await?;
let bytes: Vec<u8> = FromHex::from_hex(&hex).map_err(|_| RpcError::InvalidResponse)?;
let tx: Transaction = encode::deserialize(&bytes).map_err(|_| RpcError::InvalidResponse)?;
let mut tx_hash = tx.txid().as_hash().into_inner();
let mut tx_hash = *tx.txid().as_raw_hash().as_byte_array();
tx_hash.reverse();
if hash != &tx_hash {
Err(RpcError::InvalidResponse)?;

View file

@ -14,8 +14,8 @@ use frost::{
use bitcoin::{
consensus::encode::{Decodable, serialize},
schnorr::TweakedPublicKey,
OutPoint, Script, TxOut, Transaction, Block, Network, Address,
key::TweakedPublicKey,
OutPoint, ScriptBuf, TxOut, Transaction, Block, Network, Address,
};
use crate::crypto::{x_only, make_even};
@ -95,7 +95,7 @@ impl ReceivedOutput {
#[derive(Clone, Debug)]
pub struct Scanner {
key: ProjectivePoint,
scripts: HashMap<Script, Scalar>,
scripts: HashMap<ScriptBuf, Scalar>,
}
impl Scanner {

View file

@ -13,9 +13,10 @@ use k256::{elliptic_curve::sec1::ToEncodedPoint, Scalar};
use frost::{curve::Secp256k1, Participant, ThresholdKeys, FrostError, sign::*};
use bitcoin::{
hashes::Hash,
util::sighash::{SchnorrSighashType, SighashCache, Prevouts},
OutPoint, Script, Sequence, Witness, TxIn, TxOut, PackedLockTime, Transaction, Network, Address,
sighash::{TapSighashType, SighashCache, Prevouts},
absolute::LockTime,
script::{PushBytesBuf, ScriptBuf},
OutPoint, Sequence, Witness, TxIn, TxOut, Transaction, Network, Address,
};
use crate::{
@ -61,18 +62,18 @@ impl SignableTransaction {
// Expand this a full transaction in order to use the bitcoin library's weight function
let mut tx = Transaction {
version: 2,
lock_time: PackedLockTime::ZERO,
lock_time: LockTime::ZERO,
input: vec![
TxIn {
// This is a fixed size
// See https://developer.bitcoin.org/reference/transactions.html#raw-transaction-format
previous_output: OutPoint::default(),
// This is empty for a Taproot spend
script_sig: Script::new(),
script_sig: ScriptBuf::new(),
// This is fixed size, yet we do use Sequence::MAX
sequence: Sequence::MAX,
// Our witnesses contains a single 64-byte signature
witness: Witness::from_vec(vec![vec![0; 64]])
witness: Witness::from_slice(&[vec![0; 64]])
};
inputs
],
@ -137,7 +138,7 @@ impl SignableTransaction {
.iter()
.map(|input| TxIn {
previous_output: input.outpoint,
script_sig: Script::new(),
script_sig: ScriptBuf::new(),
sequence: Sequence::MAX,
witness: Witness::new(),
})
@ -151,7 +152,13 @@ impl SignableTransaction {
// Add the OP_RETURN output
if let Some(data) = data {
tx_outs.push(TxOut { value: 0, script_pubkey: Script::new_op_return(&data) })
tx_outs.push(TxOut {
value: 0,
script_pubkey: ScriptBuf::new_op_return(
&PushBytesBuf::try_from(data)
.expect("data didn't fit into PushBytes depsite being checked"),
),
})
}
let mut weight = Self::calculate_weight(tx_ins.len(), payments, None);
@ -182,12 +189,7 @@ impl SignableTransaction {
}
Ok(SignableTransaction {
tx: Transaction {
version: 2,
lock_time: PackedLockTime::ZERO,
input: tx_ins,
output: tx_outs,
},
tx: Transaction { version: 2, lock_time: LockTime::ZERO, input: tx_ins, output: tx_outs },
offsets,
prevouts: inputs.drain(..).map(|input| input.output).collect(),
needed_fee,
@ -208,7 +210,7 @@ impl SignableTransaction {
// Transcript the inputs and outputs
let tx = &self.tx;
for input in &tx.input {
transcript.append_message(b"input_hash", input.previous_output.txid.as_hash().into_inner());
transcript.append_message(b"input_hash", input.previous_output.txid);
transcript.append_message(b"input_output_index", input.previous_output.vout.to_le_bytes());
}
for payment in &tx.output {
@ -335,9 +337,10 @@ impl SignMachine<Transaction> for TransactionSignMachine {
.map(|(i, sig)| {
let (sig, share) = sig.sign(
commitments[i].clone(),
&cache
.taproot_key_spend_signature_hash(i, &prevouts, SchnorrSighashType::Default)
.unwrap(),
cache
.taproot_key_spend_signature_hash(i, &prevouts, TapSighashType::Default)
.unwrap()
.as_ref(),
)?;
shares.push(share);
Ok(sig)
@ -369,7 +372,7 @@ impl SignatureMachine<Transaction> for TransactionSignatureMachine {
shares.iter_mut().map(|(l, shares)| (*l, shares.remove(0))).collect::<HashMap<_, _>>(),
)?;
let mut witness: Witness = Witness::new();
let mut witness = Witness::new();
witness.push(sig.as_ref());
input.witness = witness;
}

View file

@ -18,7 +18,7 @@ async_sequential! {
// Test get_block by checking the received block's hash matches the request
let block = rpc.get_block(&hash).await.unwrap();
// Hashes are stored in reverse. It's bs from Satoshi
let mut block_hash = block.block_hash().as_hash().into_inner();
let mut block_hash = *block.block_hash().as_raw_hash().as_byte_array();
block_hash.reverse();
assert_eq!(hash, block_hash);
}

View file

@ -20,11 +20,9 @@ use frost::{
use bitcoin_serai::{
bitcoin::{
hashes::Hash as HashTrait,
blockdata::{
opcodes::all::OP_RETURN,
script::{Instruction, Instructions},
},
OutPoint, Script, TxOut, Transaction, Network, Address,
blockdata::opcodes::all::OP_RETURN,
script::{PushBytesBuf, Instruction, Instructions, Script},
OutPoint, TxOut, Transaction, Network, Address,
},
wallet::{tweak_keys, address, ReceivedOutput, Scanner, TransactionError, SignableTransaction},
rpc::Rpc,
@ -54,7 +52,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::new(), Network::Regtest).unwrap()]),
serde_json::json!([100, Address::p2sh(Script::empty(), Network::Regtest).unwrap()]),
)
.await
.unwrap();
@ -306,7 +304,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_hash().into_inner();
let mut hash = *tx.txid().as_raw_hash().as_byte_array();
hash.reverse();
assert_eq!(tx, rpc.get_transaction(&hash).await.unwrap());
}
@ -338,7 +336,10 @@ async_sequential! {
assert!(tx.output[0].script_pubkey.is_op_return());
let check = |mut instructions: Instructions| {
assert_eq!(instructions.next().unwrap().unwrap(), Instruction::Op(OP_RETURN));
assert_eq!(instructions.next().unwrap().unwrap(), Instruction::PushBytes(&data));
assert_eq!(
instructions.next().unwrap().unwrap(),
Instruction::PushBytes(&PushBytesBuf::try_from(data.clone()).unwrap()),
);
assert!(instructions.next().is_none());
};
check(tx.output[0].script_pubkey.instructions());

View file

@ -55,7 +55,7 @@ dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.3" }
monero-generators = { path = "generators", version = "0.3" }
[dev-dependencies]
hex-literal = "0.3"
hex-literal = "0.4"
tokio = { version = "1", features = ["full"] }
monero-rpc = "0.3"

View file

@ -29,7 +29,7 @@ group = "0.13"
multiexp = { path = "../multiexp", version = "0.3", features = ["batch"], optional = true }
[dev-dependencies]
hex-literal = "0.3"
hex-literal = "0.4"
blake2 = "0.10"

View file

@ -38,7 +38,7 @@ transcript = { package = "flexible-transcript", path = "../crypto/transcript" }
frost = { package = "modular-frost", path = "../crypto/frost", features = ["ristretto"] }
# Bitcoin
secp256k1 = { version = "0.24", features = ["global-context", "rand-std"], optional = true }
secp256k1 = { version = "0.27", features = ["global-context", "rand-std"], optional = true }
k256 = { version = "0.13", features = ["arithmetic"], optional = true }
bitcoin-serai = { path = "../coins/bitcoin", optional = true }

View file

@ -16,10 +16,8 @@ use bitcoin_serai::{
bitcoin::{
hashes::Hash as HashTrait,
consensus::{Encodable, Decodable},
psbt::serialize::Serialize,
OutPoint,
blockdata::script::Instruction,
Transaction, Block, Network,
script::Instruction,
OutPoint, Transaction, Block, Network,
},
wallet::{
tweak_keys, address, ReceivedOutput, Scanner, TransactionError,
@ -31,9 +29,11 @@ use bitcoin_serai::{
#[cfg(test)]
use bitcoin_serai::bitcoin::{
secp256k1::{SECP256K1, SecretKey, Message},
PrivateKey, PublicKey, EcdsaSighashType,
blockdata::script::Builder,
PackedLockTime, Sequence, Script, Witness, TxIn, TxOut, Address as BAddress,
PrivateKey, PublicKey,
sighash::{EcdsaSighashType, SighashCache},
script::{PushBytesBuf, Builder},
absolute::LockTime,
Sequence, Script, Witness, TxIn, TxOut, Address as BAddress,
};
use serai_client::{
@ -134,19 +134,21 @@ pub struct Fee(u64);
impl TransactionTrait<Bitcoin> for Transaction {
type Id = [u8; 32];
fn id(&self) -> Self::Id {
let mut hash = self.txid().as_hash().into_inner();
let mut hash = *self.txid().as_raw_hash().as_byte_array();
hash.reverse();
hash
}
fn serialize(&self) -> Vec<u8> {
Serialize::serialize(self)
let mut buf = vec![];
self.consensus_encode(&mut buf).unwrap();
buf
}
#[cfg(test)]
async fn fee(&self, coin: &Bitcoin) -> u64 {
let mut value = 0;
for input in &self.input {
let output = input.previous_output;
let mut hash = output.txid.as_hash().into_inner();
let mut hash = *output.txid.as_raw_hash().as_byte_array();
hash.reverse();
value += coin.rpc.get_transaction(&hash).await.unwrap().output
[usize::try_from(output.vout).unwrap()]
@ -191,7 +193,7 @@ impl Eq for SignableTransaction {}
impl BlockTrait<Bitcoin> for Block {
type Id = [u8; 32];
fn id(&self) -> Self::Id {
let mut hash = self.block_hash().as_hash().into_inner();
let mut hash = *self.block_hash().as_raw_hash().as_byte_array();
hash.reverse();
hash
}
@ -350,7 +352,7 @@ impl Coin for Bitcoin {
for output in &tx.output {
if output.script_pubkey.is_op_return() {
match output.script_pubkey.instructions_minimal().last() {
Some(Ok(Instruction::PushBytes(data))) => return data.to_vec(),
Some(Ok(Instruction::PushBytes(data))) => return data.as_bytes().to_vec(),
_ => continue,
}
}
@ -552,7 +554,7 @@ impl Coin for Bitcoin {
.rpc
.rpc_call::<Vec<String>>(
"generatetoaddress",
serde_json::json!([1, BAddress::p2sh(&Script::new(), Network::Regtest).unwrap()]),
serde_json::json!([1, BAddress::p2sh(Script::empty(), Network::Regtest).unwrap()]),
)
.await
.unwrap();
@ -579,10 +581,10 @@ impl Coin for Bitcoin {
let tx = self.get_block(new_block).await.unwrap().txdata.swap_remove(0);
let mut tx = Transaction {
version: 2,
lock_time: PackedLockTime::ZERO,
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid: tx.txid(), vout: 0 },
script_sig: Script::default(),
script_sig: Script::empty().into(),
sequence: Sequence(u32::MAX),
witness: Witness::default(),
}],
@ -595,15 +597,20 @@ impl Coin for Bitcoin {
let mut der = SECP256K1
.sign_ecdsa_low_r(
&Message::from(
tx.signature_hash(0, &main_addr.script_pubkey(), EcdsaSighashType::All.to_u32())
.as_hash(),
SighashCache::new(&tx)
.legacy_signature_hash(0, &main_addr.script_pubkey(), EcdsaSighashType::All.to_u32())
.unwrap()
.to_raw_hash(),
),
&private_key.inner,
)
.serialize_der()
.to_vec();
der.push(1);
tx.input[0].script_sig = Builder::new().push_slice(&der).push_key(&public_key).into_script();
tx.input[0].script_sig = Builder::new()
.push_slice(PushBytesBuf::try_from(der).unwrap())
.push_key(&public_key)
.into_script();
let block = self.get_latest_block_number().await.unwrap() + 1;
self.rpc.send_raw_transaction(&tx).await.unwrap();

View file

@ -164,7 +164,7 @@ impl<C: Coin, D: Db> KeyGen<C, D> {
let rng = |label, id: KeyGenId| {
let mut transcript = RecommendedTranscript::new(label);
transcript.append_message(b"entropy", self.entropy.as_ref());
transcript.append_message(b"entropy", &self.entropy);
transcript.append_message(b"context", context(&id));
ChaCha20Rng::from_seed(transcript.rng_seed(b"rng"))
};

View file

@ -178,17 +178,19 @@ async fn run<C: Coin, D: Db, Co: Coordinator>(raw_db: D, coin: C, mut coordinato
}
let bytes = Zeroizing::new(hex::decode(entropy).expect("entropy wasn't hex-formatted"));
let mut entropy = Zeroizing::new([0; 32]);
entropy.as_mut().copy_from_slice(bytes.as_ref());
let entropy_mut: &mut [u8] = entropy.as_mut();
entropy_mut.copy_from_slice(bytes.as_ref());
let mut transcript = RecommendedTranscript::new(b"Serai Processor Entropy");
transcript.append_message(b"entropy", entropy.as_ref());
transcript.append_message(b"entropy", entropy);
transcript
};
let mut entropy = |label| {
let mut challenge = entropy_transcript.challenge(label);
let mut res = Zeroizing::new([0; 32]);
res.as_mut().copy_from_slice(&challenge[.. 32]);
let res_mut: &mut [u8] = res.as_mut();
res_mut.copy_from_slice(&challenge[.. 32]);
challenge.zeroize();
res
};

View file

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

View file

@ -6,16 +6,40 @@ use bitcoin::{
hashes::{Hash as HashTrait, hash160::Hash},
PubkeyHash, ScriptHash,
network::constants::Network,
util::address::{Error, WitnessVersion, Payload, Address as BAddress},
address::{
Error, WitnessVersion, Payload, WitnessProgram, NetworkChecked, Address as BAddressGeneric,
},
};
#[derive(Clone, PartialEq, Eq, Debug)]
type BAddress = BAddressGeneric<NetworkChecked>;
#[derive(Clone, Eq, Debug)]
pub struct Address(pub BAddress);
impl PartialEq for Address {
fn eq(&self, other: &Self) -> bool {
self.0.payload == other.0.payload
}
}
impl FromStr for Address {
type Err = Error;
fn from_str(str: &str) -> Result<Address, Error> {
BAddress::from_str(str).map(Address)
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))
}
}
@ -38,26 +62,26 @@ enum EncodedAddress {
impl TryFrom<Vec<u8>> for Address {
type Error = ();
fn try_from(data: Vec<u8>) -> Result<Address, ()> {
Ok(Address(BAddress {
network: Network::Bitcoin,
payload: match EncodedAddress::decode(&mut data.as_ref()).map_err(|_| ())? {
Ok(Address(BAddress::new(
Network::Bitcoin,
match EncodedAddress::decode(&mut data.as_ref()).map_err(|_| ())? {
EncodedAddress::P2PKH(hash) => {
Payload::PubkeyHash(PubkeyHash::from_hash(Hash::from_inner(hash)))
Payload::PubkeyHash(PubkeyHash::from_raw_hash(Hash::from_byte_array(hash)))
}
EncodedAddress::P2SH(hash) => {
Payload::ScriptHash(ScriptHash::from_hash(Hash::from_inner(hash)))
Payload::ScriptHash(ScriptHash::from_raw_hash(Hash::from_byte_array(hash)))
}
EncodedAddress::P2WPKH(hash) => {
Payload::WitnessProgram { version: WitnessVersion::V0, program: hash.to_vec() }
Payload::WitnessProgram(WitnessProgram::new(WitnessVersion::V0, hash).unwrap())
}
EncodedAddress::P2WSH(hash) => {
Payload::WitnessProgram { version: WitnessVersion::V0, program: hash.to_vec() }
Payload::WitnessProgram(WitnessProgram::new(WitnessVersion::V0, hash).unwrap())
}
EncodedAddress::P2TR(key) => {
Payload::WitnessProgram { version: WitnessVersion::V1, program: key.to_vec() }
Payload::WitnessProgram(WitnessProgram::new(WitnessVersion::V1, key).unwrap())
}
},
}))
)))
}
}
@ -67,21 +91,30 @@ impl TryInto<Vec<u8>> for Address {
fn try_into(self) -> Result<Vec<u8>, ()> {
Ok(
(match self.0.payload {
Payload::PubkeyHash(hash) => EncodedAddress::P2PKH(hash.as_hash().into_inner()),
Payload::ScriptHash(hash) => EncodedAddress::P2SH(hash.as_hash().into_inner()),
Payload::WitnessProgram { version: WitnessVersion::V0, program } => {
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 {
EncodedAddress::P2WPKH(program.try_into().map_err(|_| ())?)
let mut buf = [0; 20];
buf.copy_from_slice(program.as_ref());
EncodedAddress::P2WPKH(buf)
} else if program.len() == 32 {
EncodedAddress::P2WSH(program.try_into().map_err(|_| ())?)
let mut buf = [0; 32];
buf.copy_from_slice(program.as_ref());
EncodedAddress::P2WSH(buf)
} else {
Err(())?
}
}
Payload::WitnessProgram { version: WitnessVersion::V1, program } => {
EncodedAddress::P2TR(program.try_into().map_err(|_| ())?)
WitnessVersion::V1 => {
let program_ref: &[u8] = program.program().as_ref();
EncodedAddress::P2TR(program_ref.try_into().map_err(|_| ())?)
}
_ => Err(())?,
},
_ => Err(())?,
})
.encode(),
)

View file

@ -68,8 +68,8 @@ impl TryFrom<Vec<u8>> for Address {
}
},
),
Ed25519::read_G(&mut addr.spend.as_ref()).map_err(|_| ())?.0,
Ed25519::read_G(&mut addr.view.as_ref()).map_err(|_| ())?.0,
Ed25519::read_G::<&[u8]>(&mut addr.spend.as_ref()).map_err(|_| ())?.0,
Ed25519::read_G::<&[u8]>(&mut addr.view.as_ref()).map_err(|_| ())?.0,
)))
}
}