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
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,
)))
}
}