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 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
|
||||||
|
|
|
@ -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)?,
|
||||||
|
|
|
@ -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]>;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue