mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-03 17:40:34 +00:00
Bitcoin External/Branch/Change addresses
Adds support for offset inputs to the Bitcoin lib
This commit is contained in:
parent
c6bd00e778
commit
a3267034b6
3 changed files with 94 additions and 24 deletions
|
@ -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
|
||||
|
|
|
@ -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)?,
|
||||
|
|
|
@ -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]>;
|
||||
|
||||
|
|
Loading…
Reference in a new issue