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 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::{
hashes::Hash,
@ -21,6 +26,7 @@ use crate::crypto::{BitcoinHram, make_even};
#[derive(Clone, Debug)]
pub struct SpendableOutput {
pub offset: Scalar,
pub output: TxOut,
pub outpoint: OutPoint,
}
@ -32,6 +38,7 @@ impl SpendableOutput {
pub fn read<R: Read>(r: &mut R) -> io::Result<SpendableOutput> {
Ok(SpendableOutput {
offset: Secp256k1::read_F(r)?,
output: TxOut::consensus_decode(r)
.map_err(|_| io::Error::new(io::ErrorKind::Other, "invalid TxOut"))?,
outpoint: OutPoint::consensus_decode(r)
@ -40,14 +47,15 @@ impl SpendableOutput {
}
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();
res
}
}
#[derive(Clone, Debug)]
pub struct SignableTransaction(Transaction, Vec<TxOut>);
pub struct SignableTransaction(Transaction, Vec<Scalar>, Vec<TxOut>);
impl SignableTransaction {
fn calculate_weight(inputs: usize, payments: &[(Address, u64)], change: Option<&Address>) -> u64 {
@ -81,6 +89,7 @@ impl SignableTransaction {
fee: u64,
) -> Option<SignableTransaction> {
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
.iter()
.map(|input| TxIn {
@ -116,6 +125,7 @@ impl SignableTransaction {
Some(SignableTransaction(
Transaction { version: 2, lock_time: PackedLockTime::ZERO, input: tx_ins, output: tx_outs },
offsets,
inputs.drain(..).map(|input| input.output).collect(),
))
}
@ -140,10 +150,14 @@ impl SignableTransaction {
}
let mut sigs = vec![];
for _ in 0 .. tx.input.len() {
for i in 0 .. tx.input.len() {
// TODO: Use the above transcript here
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<_>>();
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 sigs = self

View file

@ -1,4 +1,4 @@
use std::io;
use std::{io, collections::HashMap};
use async_trait::async_trait;
@ -41,13 +41,12 @@ impl BlockTrait for Block {
pub struct Fee(u64);
#[derive(Clone, Debug)]
pub struct Output(SpendableOutput);
pub struct Output(SpendableOutput, OutputType);
impl OutputTrait for Output {
type Id = [u8; 36];
// TODO: Implement later
fn kind(&self) -> OutputType {
OutputType::External
self.1
}
fn id(&self) -> Self::Id {
@ -59,11 +58,13 @@ impl OutputTrait for Output {
}
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> {
SpendableOutput::read(reader).map(Output)
Ok(Output(SpendableOutput::read(reader)?, OutputType::read(reader)?))
}
}
@ -74,10 +75,32 @@ pub struct SignableTransaction {
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)]
pub struct Bitcoin {
pub(crate) rpc: Rpc,
}
impl Bitcoin {
pub async fn new(url: String) -> Bitcoin {
Bitcoin { rpc: Rpc::new(url) }
@ -129,9 +152,8 @@ impl Coin for Bitcoin {
)
}
// TODO: Implement later
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> {
@ -149,17 +171,31 @@ impl Coin for Bitcoin {
block: &Self::Block,
key: ProjectivePoint,
) -> 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();
// Skip the coinbase transaction which is burdened by maturity
for tx in &block.txdata[1 ..] {
for (vout, output) in tx.output.iter().enumerate() {
if output.script_pubkey == main_addr.script_pubkey() {
outputs.push(Output(SpendableOutput {
if let Some(info) = scripts.get(&output.script_pubkey.to_bytes()) {
outputs.push(Output(
SpendableOutput {
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,
mut inputs: Vec<Output>,
payments: &[(Address, u64)],
change: Option<ProjectivePoint>,
change_key: Option<ProjectivePoint>,
fee: Fee,
) -> Result<Self::SignableTransaction, CoinError> {
Ok(SignableTransaction {
@ -183,8 +219,7 @@ impl Coin for Bitcoin {
actual: BSignableTransaction::new(
inputs.drain(..).map(|input| input.0).collect(),
payments,
// TODO: Diversify to a proper change address
change.map(|change| self.address(change)),
change_key.map(|change_key| self.address(change(change_key).0)),
fee.0,
)
.ok_or(CoinError::NotEnoughFunds)?,

View file

@ -1,4 +1,4 @@
use std::marker::Send;
use std::io;
use async_trait::async_trait;
use thiserror::Error;
@ -36,6 +36,27 @@ pub enum OutputType {
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 {
type Id: Clone + Copy + AsRef<[u8]>;