Bitcoin External/Branch/Change addresses

Adds support for offset inputs to the Bitcoin lib
This commit is contained in:
Luke Parker 2023-01-31 08:10:28 -05:00
parent c6bd00e778
commit a3267034b6
No known key found for this signature in database
3 changed files with 94 additions and 24 deletions

View file

@ -8,7 +8,12 @@ use rand_core::RngCore;
use transcript::{Transcript, RecommendedTranscript}; use transcript::{Transcript, RecommendedTranscript};
use k256::{elliptic_curve::sec1::ToEncodedPoint, Scalar}; use k256::{elliptic_curve::sec1::ToEncodedPoint, Scalar};
use frost::{curve::Secp256k1, ThresholdKeys, FrostError, algorithm::Schnorr, sign::*}; use frost::{
curve::{Ciphersuite, Secp256k1},
ThresholdKeys, FrostError,
algorithm::Schnorr,
sign::*,
};
use bitcoin::{ use bitcoin::{
hashes::Hash, hashes::Hash,
@ -21,6 +26,7 @@ use crate::crypto::{BitcoinHram, make_even};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SpendableOutput { pub struct SpendableOutput {
pub offset: Scalar,
pub output: TxOut, pub output: TxOut,
pub outpoint: OutPoint, pub outpoint: OutPoint,
} }
@ -32,6 +38,7 @@ impl SpendableOutput {
pub fn read<R: Read>(r: &mut R) -> io::Result<SpendableOutput> { pub fn read<R: Read>(r: &mut R) -> io::Result<SpendableOutput> {
Ok(SpendableOutput { Ok(SpendableOutput {
offset: Secp256k1::read_F(r)?,
output: TxOut::consensus_decode(r) output: TxOut::consensus_decode(r)
.map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid TxOut"))?, .map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid TxOut"))?,
outpoint: OutPoint::consensus_decode(r) outpoint: OutPoint::consensus_decode(r)
@ -40,14 +47,15 @@ impl SpendableOutput {
} }
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
let mut res = serialize(&self.output); let mut res = self.offset.to_bytes().to_vec();
self.output.consensus_encode(&mut res).unwrap();
self.outpoint.consensus_encode(&mut res).unwrap(); self.outpoint.consensus_encode(&mut res).unwrap();
res res
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SignableTransaction(Transaction, Vec<TxOut>); pub struct SignableTransaction(Transaction, Vec<Scalar>, Vec<TxOut>);
impl SignableTransaction { impl SignableTransaction {
fn calculate_weight(inputs: usize, payments: &[(Address, u64)], change: Option<&Address>) -> u64 { fn calculate_weight(inputs: usize, payments: &[(Address, u64)], change: Option<&Address>) -> u64 {
@ -81,6 +89,7 @@ impl SignableTransaction {
fee: u64, fee: u64,
) -> Option<SignableTransaction> { ) -> Option<SignableTransaction> {
let input_sat = inputs.iter().map(|input| input.output.value).sum::<u64>(); let input_sat = inputs.iter().map(|input| input.output.value).sum::<u64>();
let offsets = inputs.iter().map(|input| input.offset).collect();
let tx_ins = inputs let tx_ins = inputs
.iter() .iter()
.map(|input| TxIn { .map(|input| TxIn {
@ -116,6 +125,7 @@ impl SignableTransaction {
Some(SignableTransaction( Some(SignableTransaction(
Transaction { version: 2, lock_time: PackedLockTime::ZERO, input: tx_ins, output: tx_outs }, Transaction { version: 2, lock_time: PackedLockTime::ZERO, input: tx_ins, output: tx_outs },
offsets,
inputs.drain(..).map(|input| input.output).collect(), inputs.drain(..).map(|input| input.output).collect(),
)) ))
} }
@ -140,10 +150,14 @@ impl SignableTransaction {
} }
let mut sigs = vec![]; let mut sigs = vec![];
for _ in 0 .. tx.input.len() { for i in 0 .. tx.input.len() {
// TODO: Use the above transcript here // TODO: Use the above transcript here
sigs.push( sigs.push(
AlgorithmMachine::new(Schnorr::<Secp256k1, BitcoinHram>::new(), keys.clone()).unwrap(), AlgorithmMachine::new(
Schnorr::<Secp256k1, BitcoinHram>::new(),
keys.clone().offset(self.1[i]),
)
.unwrap(),
); );
} }
@ -237,7 +251,7 @@ impl SignMachine<Transaction> for TransactionSignMachine {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut cache = SighashCache::new(&self.tx.0); let mut cache = SighashCache::new(&self.tx.0);
let prevouts = Prevouts::All(&self.tx.1); let prevouts = Prevouts::All(&self.tx.2);
let mut shares = Vec::with_capacity(self.sigs.len()); let mut shares = Vec::with_capacity(self.sigs.len());
let sigs = self let sigs = self

View file

@ -1,4 +1,4 @@
use std::io; use std::{io, collections::HashMap};
use async_trait::async_trait; use async_trait::async_trait;
@ -41,13 +41,12 @@ impl BlockTrait for Block {
pub struct Fee(u64); pub struct Fee(u64);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Output(SpendableOutput); pub struct Output(SpendableOutput, OutputType);
impl OutputTrait for Output { impl OutputTrait for Output {
type Id = [u8; 36]; type Id = [u8; 36];
// TODO: Implement later
fn kind(&self) -> OutputType { fn kind(&self) -> OutputType {
OutputType::External self.1
} }
fn id(&self) -> Self::Id { fn id(&self) -> Self::Id {
@ -59,11 +58,13 @@ impl OutputTrait for Output {
} }
fn serialize(&self) -> Vec<u8> { fn serialize(&self) -> Vec<u8> {
self.0.serialize() let mut res = self.0.serialize();
self.1.write(&mut res).unwrap();
res
} }
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> { fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
SpendableOutput::read(reader).map(Output) Ok(Output(SpendableOutput::read(reader)?, OutputType::read(reader)?))
} }
} }
@ -74,10 +75,32 @@ pub struct SignableTransaction {
actual: BSignableTransaction, actual: BSignableTransaction,
} }
fn next_key(mut key: ProjectivePoint, i: usize) -> (ProjectivePoint, Scalar) {
let mut offset = Scalar::ZERO;
for _ in 0 .. i {
key += ProjectivePoint::GENERATOR;
offset += Scalar::ONE;
let even_offset;
(key, even_offset) = make_even(key);
offset += Scalar::from(even_offset);
}
(key, offset)
}
fn branch(key: ProjectivePoint) -> (ProjectivePoint, Scalar) {
next_key(key, 1)
}
fn change(key: ProjectivePoint) -> (ProjectivePoint, Scalar) {
next_key(key, 2)
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Bitcoin { pub struct Bitcoin {
pub(crate) rpc: Rpc, pub(crate) rpc: Rpc,
} }
impl Bitcoin { impl Bitcoin {
pub async fn new(url: String) -> Bitcoin { pub async fn new(url: String) -> Bitcoin {
Bitcoin { rpc: Rpc::new(url) } Bitcoin { rpc: Rpc::new(url) }
@ -129,9 +152,8 @@ impl Coin for Bitcoin {
) )
} }
// TODO: Implement later
fn branch_address(&self, key: ProjectivePoint) -> Self::Address { fn branch_address(&self, key: ProjectivePoint) -> Self::Address {
self.address(key) self.address(branch(key).0)
} }
async fn get_latest_block_number(&self) -> Result<usize, CoinError> { async fn get_latest_block_number(&self) -> Result<usize, CoinError> {
@ -149,17 +171,31 @@ impl Coin for Bitcoin {
block: &Self::Block, block: &Self::Block,
key: ProjectivePoint, key: ProjectivePoint,
) -> Result<Vec<Self::Output>, CoinError> { ) -> Result<Vec<Self::Output>, CoinError> {
let main_addr = self.address(key); let external = (key, Scalar::ZERO);
let branch = branch(key);
let change = change(key);
let entry =
|pair: (_, _), kind| (self.address(pair.0).script_pubkey().to_bytes(), (pair.1, kind));
let scripts = HashMap::from([
entry(external, OutputType::External),
entry(branch, OutputType::Branch),
entry(change, OutputType::Change),
]);
let mut outputs = Vec::new(); let mut outputs = Vec::new();
// Skip the coinbase transaction which is burdened by maturity // Skip the coinbase transaction which is burdened by maturity
for tx in &block.txdata[1 ..] { for tx in &block.txdata[1 ..] {
for (vout, output) in tx.output.iter().enumerate() { for (vout, output) in tx.output.iter().enumerate() {
if output.script_pubkey == main_addr.script_pubkey() { if let Some(info) = scripts.get(&output.script_pubkey.to_bytes()) {
outputs.push(Output(SpendableOutput { outputs.push(Output(
output: output.clone(), SpendableOutput {
outpoint: OutPoint { txid: tx.txid(), vout: u32::try_from(vout).unwrap() }, offset: info.0,
})); output: output.clone(),
outpoint: OutPoint { txid: tx.txid(), vout: u32::try_from(vout).unwrap() },
},
info.1,
));
} }
} }
} }
@ -174,7 +210,7 @@ impl Coin for Bitcoin {
_: usize, _: usize,
mut inputs: Vec<Output>, mut inputs: Vec<Output>,
payments: &[(Address, u64)], payments: &[(Address, u64)],
change: Option<ProjectivePoint>, change_key: Option<ProjectivePoint>,
fee: Fee, fee: Fee,
) -> Result<Self::SignableTransaction, CoinError> { ) -> Result<Self::SignableTransaction, CoinError> {
Ok(SignableTransaction { Ok(SignableTransaction {
@ -183,8 +219,7 @@ impl Coin for Bitcoin {
actual: BSignableTransaction::new( actual: BSignableTransaction::new(
inputs.drain(..).map(|input| input.0).collect(), inputs.drain(..).map(|input| input.0).collect(),
payments, payments,
// TODO: Diversify to a proper change address change_key.map(|change_key| self.address(change(change_key).0)),
change.map(|change| self.address(change)),
fee.0, fee.0,
) )
.ok_or(CoinError::NotEnoughFunds)?, .ok_or(CoinError::NotEnoughFunds)?,

View file

@ -1,4 +1,4 @@
use std::marker::Send; use std::io;
use async_trait::async_trait; use async_trait::async_trait;
use thiserror::Error; use thiserror::Error;
@ -36,6 +36,27 @@ pub enum OutputType {
Change, Change,
} }
impl OutputType {
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&[match self {
OutputType::External => 0,
OutputType::Branch => 1,
OutputType::Change => 2,
}])
}
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
let mut byte = [0; 1];
reader.read_exact(&mut byte)?;
Ok(match byte[0] {
0 => OutputType::External,
1 => OutputType::Branch,
2 => OutputType::Change,
_ => Err(io::Error::new(io::ErrorKind::Other, "invalid OutputType"))?,
})
}
}
pub trait Output: Sized + Clone { pub trait Output: Sized + Clone {
type Id: Clone + Copy + AsRef<[u8]>; type Id: Clone + Copy + AsRef<[u8]>;