mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-09 12:29:27 +00:00
Bitcoin Output/Transaction definitions
This commit is contained in:
parent
0ccf71df1e
commit
247cc8f0cc
17 changed files with 504 additions and 299 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -8127,18 +8127,21 @@ dependencies = [
|
|||
"async-trait",
|
||||
"bitcoin-serai",
|
||||
"borsh",
|
||||
"const-hex",
|
||||
"ciphersuite",
|
||||
"env_logger",
|
||||
"hex",
|
||||
"k256",
|
||||
"flexible-transcript",
|
||||
"log",
|
||||
"modular-frost",
|
||||
"parity-scale-codec",
|
||||
"rand_core",
|
||||
"secp256k1",
|
||||
"serai-client",
|
||||
"serai-db",
|
||||
"serai-env",
|
||||
"serai-message-queue",
|
||||
"serai-processor-messages",
|
||||
"serde_json",
|
||||
"serai-processor-primitives",
|
||||
"serai-processor-scheduler-primitives",
|
||||
"tokio",
|
||||
"zalloc",
|
||||
]
|
||||
|
@ -8151,6 +8154,7 @@ dependencies = [
|
|||
"bitcoin",
|
||||
"bitvec",
|
||||
"blake2",
|
||||
"borsh",
|
||||
"ciphersuite",
|
||||
"dockertest",
|
||||
"frame-system",
|
||||
|
|
|
@ -18,14 +18,15 @@ workspace = true
|
|||
|
||||
[dependencies]
|
||||
async-trait = { version = "0.1", default-features = false }
|
||||
rand_core = { version = "0.6", default-features = false }
|
||||
|
||||
const-hex = { version = "1", default-features = false }
|
||||
hex = { version = "0.4", default-features = false, features = ["std"] }
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||
serde_json = { version = "1", default-features = false, features = ["std"] }
|
||||
|
||||
k256 = { version = "^0.13.1", default-features = false, features = ["std"] }
|
||||
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", default-features = false, features = ["std", "recommended"] }
|
||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std", "secp256k1"] }
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false }
|
||||
|
||||
secp256k1 = { version = "0.29", default-features = false, features = ["std", "global-context", "rand-std"] }
|
||||
bitcoin-serai = { path = "../../networks/bitcoin", default-features = false, features = ["std"] }
|
||||
|
||||
|
@ -37,8 +38,13 @@ zalloc = { path = "../../common/zalloc" }
|
|||
serai-db = { path = "../../common/db" }
|
||||
serai-env = { path = "../../common/env" }
|
||||
|
||||
serai-client = { path = "../../substrate/client", default-features = false, features = ["bitcoin"] }
|
||||
|
||||
messages = { package = "serai-processor-messages", path = "../messages" }
|
||||
|
||||
primitives = { package = "serai-processor-primitives", path = "../primitives" }
|
||||
scheduler = { package = "serai-processor-scheduler-primitives", path = "../scheduler/primitives" }
|
||||
|
||||
message-queue = { package = "serai-message-queue", path = "../../message-queue" }
|
||||
|
||||
[features]
|
||||
|
|
0
processor/bitcoin/src/block.rs
Normal file
0
processor/bitcoin/src/block.rs
Normal file
|
@ -2,7 +2,15 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use std::{sync::OnceLock, time::Duration, io, collections::HashMap};
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: zalloc::ZeroizingAlloc<std::alloc::System> =
|
||||
zalloc::ZeroizingAlloc(std::alloc::System);
|
||||
|
||||
mod output;
|
||||
mod transaction;
|
||||
|
||||
/*
|
||||
use std::{sync::LazyLock, time::Duration, io, collections::HashMap};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
|
@ -49,127 +57,9 @@ use serai_client::{
|
|||
primitives::{MAX_DATA_LEN, Coin, NetworkId, Amount, Balance},
|
||||
networks::bitcoin::Address,
|
||||
};
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
networks::{
|
||||
NetworkError, Block as BlockTrait, OutputType, Output as OutputTrait,
|
||||
Transaction as TransactionTrait, SignableTransaction as SignableTransactionTrait,
|
||||
Eventuality as EventualityTrait, EventualitiesTracker, Network, UtxoNetwork,
|
||||
},
|
||||
Payment,
|
||||
multisigs::scheduler::utxo::Scheduler,
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct OutputId(pub [u8; 36]);
|
||||
impl Default for OutputId {
|
||||
fn default() -> Self {
|
||||
Self([0; 36])
|
||||
}
|
||||
}
|
||||
impl AsRef<[u8]> for OutputId {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
impl AsMut<[u8]> for OutputId {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
self.0.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Output {
|
||||
kind: OutputType,
|
||||
presumed_origin: Option<Address>,
|
||||
output: ReceivedOutput,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl OutputTrait<Bitcoin> for Output {
|
||||
type Id = OutputId;
|
||||
|
||||
fn kind(&self) -> OutputType {
|
||||
self.kind
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
let mut res = OutputId::default();
|
||||
self.output.outpoint().consensus_encode(&mut res.as_mut()).unwrap();
|
||||
debug_assert_eq!(
|
||||
{
|
||||
let mut outpoint = vec![];
|
||||
self.output.outpoint().consensus_encode(&mut outpoint).unwrap();
|
||||
outpoint
|
||||
},
|
||||
res.as_ref().to_vec()
|
||||
);
|
||||
res
|
||||
}
|
||||
|
||||
fn tx_id(&self) -> [u8; 32] {
|
||||
let mut hash = *self.output.outpoint().txid.as_raw_hash().as_byte_array();
|
||||
hash.reverse();
|
||||
hash
|
||||
}
|
||||
|
||||
fn key(&self) -> ProjectivePoint {
|
||||
let script = &self.output.output().script_pubkey;
|
||||
assert!(script.is_p2tr());
|
||||
let Instruction::PushBytes(key) = script.instructions_minimal().last().unwrap().unwrap() else {
|
||||
panic!("last item in v1 Taproot script wasn't bytes")
|
||||
};
|
||||
let key = XOnlyPublicKey::from_slice(key.as_ref())
|
||||
.expect("last item in v1 Taproot script wasn't x-only public key");
|
||||
Secp256k1::read_G(&mut key.public_key(Parity::Even).serialize().as_slice()).unwrap() -
|
||||
(ProjectivePoint::GENERATOR * self.output.offset())
|
||||
}
|
||||
|
||||
fn presumed_origin(&self) -> Option<Address> {
|
||||
self.presumed_origin.clone()
|
||||
}
|
||||
|
||||
fn balance(&self) -> Balance {
|
||||
Balance { coin: Coin::Bitcoin, amount: Amount(self.output.value()) }
|
||||
}
|
||||
|
||||
fn data(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||
self.kind.write(writer)?;
|
||||
let presumed_origin: Option<Vec<u8>> = self.presumed_origin.clone().map(Into::into);
|
||||
writer.write_all(&presumed_origin.encode())?;
|
||||
self.output.write(writer)?;
|
||||
writer.write_all(&u16::try_from(self.data.len()).unwrap().to_le_bytes())?;
|
||||
writer.write_all(&self.data)
|
||||
}
|
||||
|
||||
fn read<R: io::Read>(mut reader: &mut R) -> io::Result<Self> {
|
||||
Ok(Output {
|
||||
kind: OutputType::read(reader)?,
|
||||
presumed_origin: {
|
||||
let mut io_reader = scale::IoReader(reader);
|
||||
let res = Option::<Vec<u8>>::decode(&mut io_reader)
|
||||
.unwrap()
|
||||
.map(|address| Address::try_from(address).unwrap());
|
||||
reader = io_reader.0;
|
||||
res
|
||||
},
|
||||
output: ReceivedOutput::read(reader)?,
|
||||
data: {
|
||||
let mut data_len = [0; 2];
|
||||
reader.read_exact(&mut data_len)?;
|
||||
|
||||
let mut data = vec![0; usize::from(u16::from_le_bytes(data_len))];
|
||||
reader.read_exact(&mut data)?;
|
||||
data
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Fee(u64);
|
||||
|
||||
|
@ -201,71 +91,6 @@ impl TransactionTrait<Bitcoin> for Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Eventuality([u8; 32]);
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Default, Debug)]
|
||||
pub struct EmptyClaim;
|
||||
impl AsRef<[u8]> for EmptyClaim {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
impl AsMut<[u8]> for EmptyClaim {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
&mut []
|
||||
}
|
||||
}
|
||||
|
||||
impl EventualityTrait for Eventuality {
|
||||
type Claim = EmptyClaim;
|
||||
type Completion = Transaction;
|
||||
|
||||
fn lookup(&self) -> Vec<u8> {
|
||||
self.0.to_vec()
|
||||
}
|
||||
|
||||
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||
let mut id = [0; 32];
|
||||
reader
|
||||
.read_exact(&mut id)
|
||||
.map_err(|_| io::Error::other("couldn't decode ID in eventuality"))?;
|
||||
Ok(Eventuality(id))
|
||||
}
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
self.0.to_vec()
|
||||
}
|
||||
|
||||
fn claim(_: &Transaction) -> EmptyClaim {
|
||||
EmptyClaim
|
||||
}
|
||||
fn serialize_completion(completion: &Transaction) -> Vec<u8> {
|
||||
let mut buf = vec![];
|
||||
completion.consensus_encode(&mut buf).unwrap();
|
||||
buf
|
||||
}
|
||||
fn read_completion<R: io::Read>(reader: &mut R) -> io::Result<Transaction> {
|
||||
Transaction::consensus_decode(&mut io::BufReader::with_capacity(0, reader))
|
||||
.map_err(|e| io::Error::other(format!("{e}")))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SignableTransaction {
|
||||
actual: BSignableTransaction,
|
||||
}
|
||||
impl PartialEq for SignableTransaction {
|
||||
fn eq(&self, other: &SignableTransaction) -> bool {
|
||||
self.actual == other.actual
|
||||
}
|
||||
}
|
||||
impl Eq for SignableTransaction {}
|
||||
impl SignableTransactionTrait for SignableTransaction {
|
||||
fn fee(&self) -> u64 {
|
||||
self.actual.fee()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl BlockTrait<Bitcoin> for Block {
|
||||
type Id = [u8; 32];
|
||||
|
@ -944,3 +769,4 @@ impl Network for Bitcoin {
|
|||
impl UtxoNetwork for Bitcoin {
|
||||
const MAX_INPUTS: usize = MAX_INPUTS;
|
||||
}
|
||||
*/
|
||||
|
|
133
processor/bitcoin/src/output.rs
Normal file
133
processor/bitcoin/src/output.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
use std::io;
|
||||
|
||||
use ciphersuite::{Ciphersuite, Secp256k1};
|
||||
|
||||
use bitcoin_serai::{
|
||||
bitcoin::{
|
||||
hashes::Hash as HashTrait,
|
||||
key::{Parity, XOnlyPublicKey},
|
||||
consensus::Encodable,
|
||||
script::Instruction,
|
||||
},
|
||||
wallet::ReceivedOutput as WalletOutput,
|
||||
};
|
||||
|
||||
use scale::{Encode, Decode, IoReader};
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
use serai_client::{
|
||||
primitives::{Coin, Amount, Balance, ExternalAddress},
|
||||
networks::bitcoin::Address,
|
||||
};
|
||||
|
||||
use primitives::{OutputType, ReceivedOutput};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug, Encode, Decode, BorshSerialize, BorshDeserialize)]
|
||||
pub(crate) struct OutputId([u8; 36]);
|
||||
impl Default for OutputId {
|
||||
fn default() -> Self {
|
||||
Self([0; 36])
|
||||
}
|
||||
}
|
||||
impl AsRef<[u8]> for OutputId {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
impl AsMut<[u8]> for OutputId {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
self.0.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub(crate) struct Output {
|
||||
kind: OutputType,
|
||||
presumed_origin: Option<Address>,
|
||||
output: WalletOutput,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ReceivedOutput<<Secp256k1 as Ciphersuite>::G, Address> for Output {
|
||||
type Id = OutputId;
|
||||
type TransactionId = [u8; 32];
|
||||
|
||||
fn kind(&self) -> OutputType {
|
||||
self.kind
|
||||
}
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
let mut id = OutputId::default();
|
||||
self.output.outpoint().consensus_encode(&mut id.as_mut()).unwrap();
|
||||
id
|
||||
}
|
||||
|
||||
fn transaction_id(&self) -> Self::TransactionId {
|
||||
self.output.outpoint().txid.to_raw_hash().to_byte_array()
|
||||
}
|
||||
|
||||
fn key(&self) -> <Secp256k1 as Ciphersuite>::G {
|
||||
// We read the key from the script pubkey so we don't have to independently store it
|
||||
let script = &self.output.output().script_pubkey;
|
||||
|
||||
// These assumptions are safe since it's an output we successfully scanned
|
||||
assert!(script.is_p2tr());
|
||||
let Instruction::PushBytes(key) = script.instructions_minimal().last().unwrap().unwrap() else {
|
||||
panic!("last item in v1 Taproot script wasn't bytes")
|
||||
};
|
||||
let key = XOnlyPublicKey::from_slice(key.as_ref())
|
||||
.expect("last item in v1 Taproot script wasn't a valid x-only public key");
|
||||
|
||||
// Convert to a full key
|
||||
let key = key.public_key(Parity::Even);
|
||||
// Convert to a k256 key (from libsecp256k1)
|
||||
let output_key = Secp256k1::read_G(&mut key.serialize().as_slice()).unwrap();
|
||||
// The output's key minus the output's offset is the root key
|
||||
output_key - (<Secp256k1 as Ciphersuite>::G::GENERATOR * self.output.offset())
|
||||
}
|
||||
|
||||
fn presumed_origin(&self) -> Option<Address> {
|
||||
self.presumed_origin.clone()
|
||||
}
|
||||
|
||||
fn balance(&self) -> Balance {
|
||||
Balance { coin: Coin::Bitcoin, amount: Amount(self.output.value()) }
|
||||
}
|
||||
|
||||
fn data(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||
self.kind.write(writer)?;
|
||||
let presumed_origin: Option<ExternalAddress> = self.presumed_origin.clone().map(Into::into);
|
||||
writer.write_all(&presumed_origin.encode())?;
|
||||
self.output.write(writer)?;
|
||||
writer.write_all(&u16::try_from(self.data.len()).unwrap().to_le_bytes())?;
|
||||
writer.write_all(&self.data)
|
||||
}
|
||||
|
||||
fn read<R: io::Read>(mut reader: &mut R) -> io::Result<Self> {
|
||||
Ok(Output {
|
||||
kind: OutputType::read(reader)?,
|
||||
presumed_origin: {
|
||||
Option::<ExternalAddress>::decode(&mut IoReader(&mut reader))
|
||||
.map_err(|e| io::Error::other(format!("couldn't decode ExternalAddress: {e:?}")))?
|
||||
.map(|address| {
|
||||
Address::try_from(address)
|
||||
.map_err(|()| io::Error::other("couldn't decode Address from ExternalAddress"))
|
||||
})
|
||||
.transpose()?
|
||||
},
|
||||
output: WalletOutput::read(reader)?,
|
||||
data: {
|
||||
let mut data_len = [0; 2];
|
||||
reader.read_exact(&mut data_len)?;
|
||||
|
||||
let mut data = vec![0; usize::from(u16::from_le_bytes(data_len))];
|
||||
reader.read_exact(&mut data)?;
|
||||
data
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
170
processor/bitcoin/src/transaction.rs
Normal file
170
processor/bitcoin/src/transaction.rs
Normal file
|
@ -0,0 +1,170 @@
|
|||
use std::io;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use transcript::{Transcript, RecommendedTranscript};
|
||||
use ciphersuite::Secp256k1;
|
||||
use frost::{dkg::ThresholdKeys, sign::PreprocessMachine};
|
||||
|
||||
use bitcoin_serai::{
|
||||
bitcoin::{
|
||||
consensus::{Encodable, Decodable},
|
||||
ScriptBuf, Transaction as BTransaction,
|
||||
},
|
||||
wallet::{
|
||||
ReceivedOutput, TransactionError, SignableTransaction as BSignableTransaction,
|
||||
TransactionMachine,
|
||||
},
|
||||
};
|
||||
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
use serai_client::networks::bitcoin::Address;
|
||||
|
||||
use crate::output::OutputId;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Transaction(BTransaction);
|
||||
|
||||
impl From<BTransaction> for Transaction {
|
||||
fn from(tx: BTransaction) -> Self {
|
||||
Self(tx)
|
||||
}
|
||||
}
|
||||
|
||||
impl scheduler::Transaction for Transaction {
|
||||
fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||
let tx =
|
||||
BTransaction::consensus_decode(&mut io::BufReader::new(reader)).map_err(io::Error::other)?;
|
||||
Ok(Self(tx))
|
||||
}
|
||||
fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
let mut writer = io::BufWriter::new(writer);
|
||||
self.0.consensus_encode(&mut writer)?;
|
||||
writer.into_inner()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct SignableTransaction {
|
||||
inputs: Vec<ReceivedOutput>,
|
||||
payments: Vec<(Address, u64)>,
|
||||
change: Option<Address>,
|
||||
data: Option<Vec<u8>>,
|
||||
fee_per_vbyte: u64,
|
||||
}
|
||||
|
||||
impl SignableTransaction {
|
||||
fn signable(self) -> Result<BSignableTransaction, TransactionError> {
|
||||
BSignableTransaction::new(
|
||||
self.inputs,
|
||||
&self
|
||||
.payments
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(address, amount)| (ScriptBuf::from(address), amount))
|
||||
.collect::<Vec<_>>(),
|
||||
self.change.map(ScriptBuf::from),
|
||||
self.data,
|
||||
self.fee_per_vbyte,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ClonableTransctionMachine(SignableTransaction, ThresholdKeys<Secp256k1>);
|
||||
impl PreprocessMachine for ClonableTransctionMachine {
|
||||
type Preprocess = <TransactionMachine as PreprocessMachine>::Preprocess;
|
||||
type Signature = <TransactionMachine as PreprocessMachine>::Signature;
|
||||
type SignMachine = <TransactionMachine as PreprocessMachine>::SignMachine;
|
||||
|
||||
fn preprocess<R: RngCore + CryptoRng>(
|
||||
self,
|
||||
rng: &mut R,
|
||||
) -> (Self::SignMachine, Self::Preprocess) {
|
||||
self
|
||||
.0
|
||||
.signable()
|
||||
.expect("signing an invalid SignableTransaction")
|
||||
.multisig(&self.1, RecommendedTranscript::new(b"Serai Processor Bitcoin Transaction"))
|
||||
.expect("incorrect keys used for SignableTransaction")
|
||||
.preprocess(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl scheduler::SignableTransaction for SignableTransaction {
|
||||
type Transaction = Transaction;
|
||||
type Ciphersuite = Secp256k1;
|
||||
type PreprocessMachine = ClonableTransctionMachine;
|
||||
|
||||
fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||
let inputs = {
|
||||
let mut input_len = [0; 4];
|
||||
reader.read_exact(&mut input_len)?;
|
||||
let mut inputs = vec![];
|
||||
for _ in 0 .. u32::from_le_bytes(input_len) {
|
||||
inputs.push(ReceivedOutput::read(reader)?);
|
||||
}
|
||||
inputs
|
||||
};
|
||||
|
||||
let payments = <_>::deserialize_reader(reader)?;
|
||||
let change = <_>::deserialize_reader(reader)?;
|
||||
let data = <_>::deserialize_reader(reader)?;
|
||||
let fee_per_vbyte = <_>::deserialize_reader(reader)?;
|
||||
|
||||
Ok(Self { inputs, payments, change, data, fee_per_vbyte })
|
||||
}
|
||||
fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
writer.write_all(&u32::try_from(self.inputs.len()).unwrap().to_le_bytes())?;
|
||||
for input in &self.inputs {
|
||||
input.write(writer)?;
|
||||
}
|
||||
|
||||
self.payments.serialize(writer)?;
|
||||
self.change.serialize(writer)?;
|
||||
self.data.serialize(writer)?;
|
||||
self.fee_per_vbyte.serialize(writer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn id(&self) -> [u8; 32] {
|
||||
self.clone().signable().unwrap().txid()
|
||||
}
|
||||
|
||||
fn sign(self, keys: ThresholdKeys<Self::Ciphersuite>) -> Self::PreprocessMachine {
|
||||
ClonableTransctionMachine(self, keys)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub(crate) struct Eventuality {
|
||||
txid: [u8; 32],
|
||||
singular_spent_output: Option<OutputId>,
|
||||
}
|
||||
|
||||
impl primitives::Eventuality for Eventuality {
|
||||
type OutputId = OutputId;
|
||||
|
||||
fn id(&self) -> [u8; 32] {
|
||||
self.txid
|
||||
}
|
||||
|
||||
// We define the lookup as our ID since the resolving transaction only has a singular possible ID
|
||||
fn lookup(&self) -> Vec<u8> {
|
||||
self.txid.to_vec()
|
||||
}
|
||||
|
||||
fn singular_spent_output(&self) -> Option<Self::OutputId> {
|
||||
self.singular_spent_output.clone()
|
||||
}
|
||||
|
||||
fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||
Self::deserialize_reader(reader)
|
||||
}
|
||||
fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
self.serialize(writer)
|
||||
}
|
||||
}
|
|
@ -46,7 +46,24 @@ pub trait Id:
|
|||
+ BorshDeserialize
|
||||
{
|
||||
}
|
||||
impl<const N: usize> Id for [u8; N] where [u8; N]: Default {}
|
||||
impl<
|
||||
I: Send
|
||||
+ Sync
|
||||
+ Clone
|
||||
+ Default
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ Hash
|
||||
+ AsRef<[u8]>
|
||||
+ AsMut<[u8]>
|
||||
+ Debug
|
||||
+ Encode
|
||||
+ Decode
|
||||
+ BorshSerialize
|
||||
+ BorshDeserialize,
|
||||
> Id for I
|
||||
{
|
||||
}
|
||||
|
||||
/// A wrapper for a group element which implements the scale/borsh traits.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
|
|
|
@ -19,10 +19,19 @@ pub trait Address:
|
|||
+ BorshSerialize
|
||||
+ BorshDeserialize
|
||||
{
|
||||
/// Write this address.
|
||||
fn write(&self, writer: &mut impl io::Write) -> io::Result<()>;
|
||||
/// Read an address.
|
||||
fn read(reader: &mut impl io::Read) -> io::Result<Self>;
|
||||
}
|
||||
// This casts a wide net, yet it only implements `Address` for things `Into<ExternalAddress>` so
|
||||
// it should only implement this for addresses
|
||||
impl<
|
||||
A: Send
|
||||
+ Sync
|
||||
+ Clone
|
||||
+ Into<ExternalAddress>
|
||||
+ TryFrom<ExternalAddress>
|
||||
+ BorshSerialize
|
||||
+ BorshDeserialize,
|
||||
> Address for A
|
||||
{
|
||||
}
|
||||
|
||||
/// The type of the output.
|
||||
|
|
|
@ -48,7 +48,7 @@ impl<A: Address> Payment<A> {
|
|||
|
||||
/// Read a Payment.
|
||||
pub fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||
let address = A::read(reader)?;
|
||||
let address = A::deserialize_reader(reader)?;
|
||||
let reader = &mut IoReader(reader);
|
||||
let balance = Balance::decode(reader).map_err(io::Error::other)?;
|
||||
let data = Option::<Vec<u8>>::decode(reader).map_err(io::Error::other)?;
|
||||
|
@ -56,7 +56,7 @@ impl<A: Address> Payment<A> {
|
|||
}
|
||||
/// Write the Payment.
|
||||
pub fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
self.address.write(writer).unwrap();
|
||||
self.address.serialize(writer)?;
|
||||
self.balance.encode_to(writer);
|
||||
self.data.encode_to(writer);
|
||||
Ok(())
|
||||
|
|
|
@ -10,7 +10,7 @@ use serai_db::{Get, DbTxn, create_db, db_channel};
|
|||
use serai_in_instructions_primitives::{InInstructionWithBalance, Batch};
|
||||
use serai_coins_primitives::OutInstructionWithBalance;
|
||||
|
||||
use primitives::{EncodableG, Address, ReceivedOutput};
|
||||
use primitives::{EncodableG, ReceivedOutput};
|
||||
|
||||
use crate::{
|
||||
lifetime::{LifetimeStage, Lifetime},
|
||||
|
@ -49,7 +49,7 @@ impl<S: ScannerFeed> OutputWithInInstruction<S> {
|
|||
let mut opt = [0xff];
|
||||
reader.read_exact(&mut opt)?;
|
||||
assert!((opt[0] == 0) || (opt[0] == 1));
|
||||
(opt[0] == 1).then(|| AddressFor::<S>::read(reader)).transpose()?
|
||||
(opt[0] == 1).then(|| AddressFor::<S>::deserialize_reader(reader)).transpose()?
|
||||
};
|
||||
let in_instruction =
|
||||
InInstructionWithBalance::decode(&mut IoReader(reader)).map_err(io::Error::other)?;
|
||||
|
@ -59,7 +59,7 @@ impl<S: ScannerFeed> OutputWithInInstruction<S> {
|
|||
self.output.write(writer)?;
|
||||
if let Some(return_address) = &self.return_address {
|
||||
writer.write_all(&[1])?;
|
||||
return_address.write(writer)?;
|
||||
return_address.serialize(writer)?;
|
||||
} else {
|
||||
writer.write_all(&[0])?;
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ impl<S: ScannerFeed> ScannerGlobalDb<S> {
|
|||
buf.read_exact(&mut opt).unwrap();
|
||||
assert!((opt[0] == 0) || (opt[0] == 1));
|
||||
|
||||
let address = (opt[0] == 1).then(|| AddressFor::<S>::read(&mut buf).unwrap());
|
||||
let address = (opt[0] == 1).then(|| AddressFor::<S>::deserialize_reader(&mut buf).unwrap());
|
||||
Some((address, InInstructionWithBalance::decode(&mut IoReader(buf)).unwrap()))
|
||||
}
|
||||
}
|
||||
|
@ -338,7 +338,7 @@ impl<S: ScannerFeed> ScanToEventualityDb<S> {
|
|||
let mut buf = vec![];
|
||||
if let Some(address) = &forward.return_address {
|
||||
buf.write_all(&[1]).unwrap();
|
||||
address.write(&mut buf).unwrap();
|
||||
address.serialize(&mut buf).unwrap();
|
||||
} else {
|
||||
buf.write_all(&[0]).unwrap();
|
||||
}
|
||||
|
@ -435,7 +435,8 @@ impl<S: ScannerFeed> Returnable<S> {
|
|||
reader.read_exact(&mut opt).unwrap();
|
||||
assert!((opt[0] == 0) || (opt[0] == 1));
|
||||
|
||||
let return_address = (opt[0] == 1).then(|| AddressFor::<S>::read(reader)).transpose()?;
|
||||
let return_address =
|
||||
(opt[0] == 1).then(|| AddressFor::<S>::deserialize_reader(reader)).transpose()?;
|
||||
|
||||
let in_instruction =
|
||||
InInstructionWithBalance::decode(&mut IoReader(reader)).map_err(io::Error::other)?;
|
||||
|
@ -444,7 +445,7 @@ impl<S: ScannerFeed> Returnable<S> {
|
|||
fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
if let Some(return_address) = &self.return_address {
|
||||
writer.write_all(&[1])?;
|
||||
return_address.write(writer)?;
|
||||
return_address.serialize(writer)?;
|
||||
} else {
|
||||
writer.write_all(&[0])?;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use std::{io, collections::HashMap};
|
|||
|
||||
use group::GroupEncoding;
|
||||
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
use serai_db::{Get, DbTxn, Db};
|
||||
|
||||
use serai_primitives::{NetworkId, Coin, Amount};
|
||||
|
@ -179,12 +180,12 @@ pub struct Return<S: ScannerFeed> {
|
|||
|
||||
impl<S: ScannerFeed> Return<S> {
|
||||
pub(crate) fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
self.address.write(writer)?;
|
||||
self.address.serialize(writer)?;
|
||||
self.output.write(writer)
|
||||
}
|
||||
|
||||
pub(crate) fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||
let address = AddressFor::<S>::read(reader)?;
|
||||
let address = AddressFor::<S>::deserialize_reader(reader)?;
|
||||
let output = OutputFor::<S>::read(reader)?;
|
||||
Ok(Return { address, output })
|
||||
}
|
||||
|
|
|
@ -4,12 +4,11 @@ use std::io::{Read, Write};
|
|||
use group::GroupEncoding;
|
||||
|
||||
use scale::{Encode, Decode, IoReader};
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
use serai_db::{Get, DbTxn, create_db};
|
||||
|
||||
use serai_primitives::Balance;
|
||||
|
||||
use primitives::Address;
|
||||
|
||||
use crate::{ScannerFeed, KeyFor, AddressFor};
|
||||
|
||||
create_db!(
|
||||
|
@ -92,7 +91,7 @@ impl<S: ScannerFeed> ReportDb<S> {
|
|||
for return_information in return_information {
|
||||
if let Some(ReturnInformation { address, balance }) = return_information {
|
||||
buf.write_all(&[1]).unwrap();
|
||||
address.write(&mut buf).unwrap();
|
||||
address.serialize(&mut buf).unwrap();
|
||||
balance.encode_to(&mut buf);
|
||||
} else {
|
||||
buf.write_all(&[0]).unwrap();
|
||||
|
@ -115,7 +114,7 @@ impl<S: ScannerFeed> ReportDb<S> {
|
|||
assert!((opt[0] == 0) || (opt[0] == 1));
|
||||
|
||||
res.push((opt[0] == 1).then(|| {
|
||||
let address = AddressFor::<S>::read(&mut buf).unwrap();
|
||||
let address = AddressFor::<S>::deserialize_reader(&mut buf).unwrap();
|
||||
let balance = Balance::decode(&mut IoReader(&mut buf)).unwrap();
|
||||
ReturnInformation { address, balance }
|
||||
}));
|
||||
|
|
|
@ -11,7 +11,7 @@ use frost::{dkg::ThresholdKeys, sign::PreprocessMachine};
|
|||
use serai_db::DbTxn;
|
||||
|
||||
/// A transaction.
|
||||
pub trait Transaction: Sized {
|
||||
pub trait Transaction: Sized + Send {
|
||||
/// Read a `Transaction`.
|
||||
fn read(reader: &mut impl io::Read) -> io::Result<Self>;
|
||||
/// Write a `Transaction`.
|
||||
|
@ -20,10 +20,12 @@ pub trait Transaction: Sized {
|
|||
|
||||
/// A signable transaction.
|
||||
pub trait SignableTransaction: 'static + Sized + Send + Sync + Clone {
|
||||
/// The underlying transaction type.
|
||||
type Transaction: Transaction;
|
||||
/// The ciphersuite used to sign this transaction.
|
||||
type Ciphersuite: Ciphersuite;
|
||||
/// The preprocess machine for the signing protocol for this transaction.
|
||||
type PreprocessMachine: Clone + PreprocessMachine<Signature: Send + Transaction>;
|
||||
type PreprocessMachine: Clone + PreprocessMachine<Signature: Send + Into<Self::Transaction>>;
|
||||
|
||||
/// Read a `SignableTransaction`.
|
||||
fn read(reader: &mut impl io::Read) -> io::Result<Self>;
|
||||
|
@ -42,8 +44,7 @@ pub trait SignableTransaction: 'static + Sized + Send + Sync + Clone {
|
|||
}
|
||||
|
||||
/// The transaction type for a SignableTransaction.
|
||||
pub type TransactionFor<ST> =
|
||||
<<ST as SignableTransaction>::PreprocessMachine as PreprocessMachine>::Signature;
|
||||
pub type TransactionFor<ST> = <ST as SignableTransaction>::Transaction;
|
||||
|
||||
mod db {
|
||||
use serai_db::{Get, DbTxn, create_db, db_channel};
|
||||
|
|
|
@ -185,6 +185,8 @@ impl<D: Db, ST: SignableTransaction, P: TransactionPublisher<TransactionFor<ST>>
|
|||
}
|
||||
}
|
||||
Response::Signature { id, signature: signed_tx } => {
|
||||
let signed_tx: TransactionFor<ST> = signed_tx.into();
|
||||
|
||||
// Save this transaction to the database
|
||||
{
|
||||
let mut buf = Vec::with_capacity(256);
|
||||
|
|
|
@ -24,6 +24,7 @@ bitvec = { version = "1", default-features = false, features = ["alloc", "serde"
|
|||
|
||||
hex = "0.4"
|
||||
scale = { package = "parity-scale-codec", version = "3" }
|
||||
borsh = { version = "1" }
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1", optional = true }
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use core::{str::FromStr, fmt};
|
||||
|
||||
use scale::{Encode, Decode};
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
use bitcoin::{
|
||||
hashes::{Hash as HashTrait, hash160::Hash},
|
||||
|
@ -10,47 +11,10 @@ use bitcoin::{
|
|||
address::{AddressType, NetworkChecked, Address as BAddress},
|
||||
};
|
||||
|
||||
#[derive(Clone, Eq, Debug)]
|
||||
pub struct Address(ScriptBuf);
|
||||
use crate::primitives::ExternalAddress;
|
||||
|
||||
impl PartialEq for Address {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// Since Serai defines the Bitcoin-address specification as a variant of the script alone,
|
||||
// define equivalency as the script alone
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Address> for ScriptBuf {
|
||||
fn from(addr: Address) -> ScriptBuf {
|
||||
addr.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Address {
|
||||
type Err = ();
|
||||
fn from_str(str: &str) -> Result<Address, ()> {
|
||||
Address::new(
|
||||
BAddress::from_str(str)
|
||||
.map_err(|_| ())?
|
||||
.require_network(Network::Bitcoin)
|
||||
.map_err(|_| ())?
|
||||
.script_pubkey(),
|
||||
)
|
||||
.ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Address {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
BAddress::<NetworkChecked>::from_script(&self.0, Network::Bitcoin)
|
||||
.map_err(|_| fmt::Error)?
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
// SCALE-encoded variant of Monero addresses.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
// SCALE-encodable representation of Bitcoin addresses, used internally.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, BorshSerialize, BorshDeserialize)]
|
||||
enum EncodedAddress {
|
||||
P2PKH([u8; 20]),
|
||||
P2SH([u8; 20]),
|
||||
|
@ -59,34 +23,13 @@ enum EncodedAddress {
|
|||
P2TR([u8; 32]),
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for Address {
|
||||
impl TryFrom<&ScriptBuf> for EncodedAddress {
|
||||
type Error = ();
|
||||
fn try_from(data: Vec<u8>) -> Result<Address, ()> {
|
||||
Ok(Address(match EncodedAddress::decode(&mut data.as_ref()).map_err(|_| ())? {
|
||||
EncodedAddress::P2PKH(hash) => {
|
||||
ScriptBuf::new_p2pkh(&PubkeyHash::from_raw_hash(Hash::from_byte_array(hash)))
|
||||
}
|
||||
EncodedAddress::P2SH(hash) => {
|
||||
ScriptBuf::new_p2sh(&ScriptHash::from_raw_hash(Hash::from_byte_array(hash)))
|
||||
}
|
||||
EncodedAddress::P2WPKH(hash) => {
|
||||
ScriptBuf::new_witness_program(&WitnessProgram::new(WitnessVersion::V0, &hash).unwrap())
|
||||
}
|
||||
EncodedAddress::P2WSH(hash) => {
|
||||
ScriptBuf::new_witness_program(&WitnessProgram::new(WitnessVersion::V0, &hash).unwrap())
|
||||
}
|
||||
EncodedAddress::P2TR(key) => {
|
||||
ScriptBuf::new_witness_program(&WitnessProgram::new(WitnessVersion::V1, &key).unwrap())
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_to_vec(addr: &Address) -> Result<Vec<u8>, ()> {
|
||||
fn try_from(script_buf: &ScriptBuf) -> Result<Self, ()> {
|
||||
// This uses mainnet as our encodings don't specify a network.
|
||||
let parsed_addr =
|
||||
BAddress::<NetworkChecked>::from_script(&addr.0, Network::Bitcoin).map_err(|_| ())?;
|
||||
Ok(
|
||||
(match parsed_addr.address_type() {
|
||||
BAddress::<NetworkChecked>::from_script(script_buf, Network::Bitcoin).map_err(|_| ())?;
|
||||
Ok(match parsed_addr.address_type() {
|
||||
Some(AddressType::P2pkh) => {
|
||||
EncodedAddress::P2PKH(*parsed_addr.pubkey_hash().unwrap().as_raw_hash().as_byte_array())
|
||||
}
|
||||
|
@ -110,23 +53,119 @@ fn try_to_vec(addr: &Address) -> Result<Vec<u8>, ()> {
|
|||
}
|
||||
_ => Err(())?,
|
||||
})
|
||||
.encode(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Address> for Vec<u8> {
|
||||
fn from(addr: Address) -> Vec<u8> {
|
||||
impl From<EncodedAddress> for ScriptBuf {
|
||||
fn from(encoded: EncodedAddress) -> Self {
|
||||
match encoded {
|
||||
EncodedAddress::P2PKH(hash) => {
|
||||
ScriptBuf::new_p2pkh(&PubkeyHash::from_raw_hash(Hash::from_byte_array(hash)))
|
||||
}
|
||||
EncodedAddress::P2SH(hash) => {
|
||||
ScriptBuf::new_p2sh(&ScriptHash::from_raw_hash(Hash::from_byte_array(hash)))
|
||||
}
|
||||
EncodedAddress::P2WPKH(hash) => {
|
||||
ScriptBuf::new_witness_program(&WitnessProgram::new(WitnessVersion::V0, &hash).unwrap())
|
||||
}
|
||||
EncodedAddress::P2WSH(hash) => {
|
||||
ScriptBuf::new_witness_program(&WitnessProgram::new(WitnessVersion::V0, &hash).unwrap())
|
||||
}
|
||||
EncodedAddress::P2TR(key) => {
|
||||
ScriptBuf::new_witness_program(&WitnessProgram::new(WitnessVersion::V1, &key).unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Bitcoin address usable with Serai.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Address(ScriptBuf);
|
||||
|
||||
// Support consuming into the underlying ScriptBuf.
|
||||
impl From<Address> for ScriptBuf {
|
||||
fn from(addr: Address) -> ScriptBuf {
|
||||
addr.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Address> for BAddress {
|
||||
fn from(addr: &Address) -> BAddress {
|
||||
// This fails if the script doesn't have an address representation, yet all our representable
|
||||
// addresses' scripts do
|
||||
BAddress::<NetworkChecked>::from_script(&addr.0, Network::Bitcoin).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// Support converting a string into an address.
|
||||
impl FromStr for Address {
|
||||
type Err = ();
|
||||
fn from_str(str: &str) -> Result<Address, ()> {
|
||||
Address::new(
|
||||
BAddress::from_str(str)
|
||||
.map_err(|_| ())?
|
||||
.require_network(Network::Bitcoin)
|
||||
.map_err(|_| ())?
|
||||
.script_pubkey(),
|
||||
)
|
||||
.ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
// Support converting an address into a string.
|
||||
impl fmt::Display for Address {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
BAddress::from(self).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ExternalAddress> for Address {
|
||||
type Error = ();
|
||||
fn try_from(data: ExternalAddress) -> Result<Address, ()> {
|
||||
// Decode as an EncodedAddress, then map to a ScriptBuf
|
||||
let mut data = data.as_ref();
|
||||
let encoded = EncodedAddress::decode(&mut data).map_err(|_| ())?;
|
||||
if !data.is_empty() {
|
||||
Err(())?
|
||||
}
|
||||
Ok(Address(ScriptBuf::from(encoded)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Address> for EncodedAddress {
|
||||
fn from(addr: Address) -> EncodedAddress {
|
||||
// Safe since only encodable addresses can be created
|
||||
try_to_vec(&addr).unwrap()
|
||||
EncodedAddress::try_from(&addr.0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Address> for ExternalAddress {
|
||||
fn from(addr: Address) -> ExternalAddress {
|
||||
// Safe since all variants are fixed-length and fit into MAX_ADDRESS_LEN
|
||||
ExternalAddress::new(EncodedAddress::from(addr).encode()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl BorshSerialize for Address {
|
||||
fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
|
||||
EncodedAddress::from(self.clone()).serialize(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl BorshDeserialize for Address {
|
||||
fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
|
||||
Ok(Self(ScriptBuf::from(EncodedAddress::deserialize_reader(reader)?)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Address {
|
||||
pub fn new(address: ScriptBuf) -> Option<Self> {
|
||||
let res = Self(address);
|
||||
if try_to_vec(&res).is_ok() {
|
||||
return Some(res);
|
||||
/// Create a new Address from a ScriptBuf.
|
||||
pub fn new(script_buf: ScriptBuf) -> Option<Self> {
|
||||
// If we can represent this Script, it's an acceptable address
|
||||
if EncodedAddress::try_from(&script_buf).is_ok() {
|
||||
return Some(Self(script_buf));
|
||||
}
|
||||
// Else, it isn't acceptable
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ pub fn borsh_deserialize_bounded_vec<R: borsh::io::Read, T: BorshDeserialize, co
|
|||
// Monero, our current longest address candidate, has a longest address of featured
|
||||
// 1 (enum) + 1 (flags) + 64 (two keys) = 66
|
||||
// When JAMTIS arrives, it'll become 112 or potentially even 142 bytes
|
||||
pub const MAX_ADDRESS_LEN: u32 = 196;
|
||||
pub const MAX_ADDRESS_LEN: u32 = 192;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
|
@ -90,10 +90,6 @@ impl ExternalAddress {
|
|||
Ok(ExternalAddress(address.try_into().map_err(|_| "address length exceeds {MAX_ADDRESS_LEN}")?))
|
||||
}
|
||||
|
||||
pub fn address(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub fn consume(self) -> Vec<u8> {
|
||||
self.0.into_inner()
|
||||
|
|
Loading…
Reference in a new issue