diff --git a/src/blockchain.rs b/src/blockchain.rs index a8f62faa..35500f9a 100644 --- a/src/blockchain.rs +++ b/src/blockchain.rs @@ -17,6 +17,11 @@ use crate::consensus::{self, deserialize, serialize, CanonicalBytes, Decodable, use crate::crypto::{Keys, Signatures}; use crate::transaction::{Buyable, Cancelable, Fundable, Lockable, Punishable, Refundable}; +pub enum Blockchain { + Bitcoin, + Monero, +} + /// Defines the type for a blockchain address, this type is used when manipulating transactions. pub trait Address { /// Defines the address format for the arbitrating blockchain. diff --git a/src/crypto.rs b/src/crypto.rs index 7131ed72..cd0d4298 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -2,11 +2,13 @@ //! sign, etc) used to create the generic framework for supporting multiple blockchains under the //! same interface. +use std::convert::TryInto; use std::error; use std::fmt::{self, Debug}; use std::io; use thiserror::Error; +use tiny_keccak::{Hasher, Keccak}; use crate::consensus::{self, CanonicalBytes, Decodable, Encodable}; @@ -230,6 +232,55 @@ impl Decodable for SharedKeyId { } } +fixed_hash::construct_fixed_hash!( + /// Result of a keccak256 commitment. + #[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate"), + )] + pub struct KeccakCommitment(32); +); + +impl KeccakCommitment { + /// Create a null commitment hash with all zeros. + pub fn null_hash() -> Self { + Self([0u8; 32]) + } + + /// Hash a stream of bytes with the Keccak-256 hash function. + pub fn new(input: [u8; 32]) -> Self { + Self(input) + } +} + +impl CanonicalBytes for KeccakCommitment { + fn as_canonical_bytes(&self) -> Vec { + (*self).to_fixed_bytes().into() + } + + fn from_canonical_bytes(bytes: &[u8]) -> Result + where + Self: Sized, + { + Ok(Self::new(bytes.try_into().map_err(consensus::Error::new)?)) + } +} + +/// Engine to produce and validate hash commitments. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CommitmentEngine; + +impl Commit for CommitmentEngine { + fn commit_to>(&self, value: T) -> KeccakCommitment { + let mut out = [0u8; 32]; + let mut keccak = Keccak::v256(); + keccak.update(value.as_ref()); + keccak.finalize(&mut out); + KeccakCommitment::new(out) + } +} + /// Required for [`Arbitrating`] and [`Accordant`] blockchains to fix the cryptographic secret key /// and public key types. The public key type is shared across the network and used in /// transactions, the secret key type is used during signing operation, proofs, etc. @@ -260,13 +311,6 @@ pub trait SharedPrivateKeys { fn shared_keys() -> Vec; } -/// Required for swaps to fix the commitments type of the keys and parameters that must go through -/// the commit/reveal scheme at the beginning of the protocol. -pub trait Commitment { - /// Commitment type used in the commit/reveal scheme during swap setup. - type Commitment: Clone + PartialEq + Eq + Debug + fmt::Display + CanonicalBytes; -} - /// Trait required for [`Arbitrating`] blockchains to define the cryptographic message format to /// sign, the signature format and adaptor signature format used in the cryptographic operations /// such as signing and verifying signatures and adaptor signatures. diff --git a/src/swap/btcxmr.rs b/src/swap/btcxmr.rs index 5bbb6593..22a044cd 100644 --- a/src/swap/btcxmr.rs +++ b/src/swap/btcxmr.rs @@ -2,8 +2,9 @@ //! accordant blockchain. use crate::consensus::{self, CanonicalBytes}; +use crate::blockchain::Blockchain; use crate::crypto::{ - self, AccordantKeyId, ArbitratingKeyId, Commit, Commitment, GenerateKey, GenerateSharedKey, + self, AccordantKeyId, ArbitratingKeyId, GenerateKey, GenerateSharedKey, KeccakCommitment, ProveCrossGroupDleq, SharedKeyId, }; use crate::monero::{self as xmr}; @@ -11,6 +12,7 @@ use crate::monero::{self as xmr}; use crate::{bitcoin::BitcoinSegwitV0, crypto::Sign, monero::Monero, swap::Swap}; use monero::cryptonote::hash::Hash; +use monero::KeyPair; #[cfg(feature = "experimental")] use ecdsa_fun::{ @@ -34,6 +36,7 @@ use bitcoin::secp256k1::{ }; use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey}; +use std::collections::HashMap; use std::str::FromStr; #[cfg(feature = "experimental")] @@ -47,24 +50,16 @@ type NonceGen = nonce::Synthetic>; pub const SHARED_KEY_BITS: usize = 252; /// The context for a Bitcoin and Monero swap. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct BtcXmr; #[cfg(feature = "experimental")] #[cfg_attr(docsrs, doc(cfg(feature = "experimental")))] impl Swap for BtcXmr { - /// The arbitrating blockchain type Ar = BitcoinSegwitV0; - - /// The accordant blockchain type Ac = Monero; - - /// The proof system to link both cryptographic groups type Proof = RingProof; -} - -impl Commitment for BtcXmr { - type Commitment = Hash; + type Commitment = KeccakCommitment; } impl CanonicalBytes for Hash { @@ -97,44 +92,84 @@ impl CanonicalBytes for RingProof { } /// Manager responsible for handling key operations (secret and public). Implements traits for -/// handling [`Commit`], [`GenerateKey`], [`GenerateSharedKey`] and [`Sign`]. -#[derive(Clone, Debug)] +/// handling [`GenerateKey`], [`GenerateSharedKey`] and [`Sign`]. +#[derive(Debug)] pub struct KeyManager { - seed: Option<[u8; 32]>, + seed: [u8; 32], + //bitcoin_master_key: ExtendedPrivKey, + //monero_master_key: ExtendedPrivKey, + bitcoin_derivations: HashMap, + monero_derivations: KeyPair, } impl KeyManager { - pub fn new(seed: [u8; 32]) -> Self { - Self { seed: Some(seed) } + //pub fn derive_blockchain_seeds(master_seed: [u8; 32], blockchain: Blockchain) -> [u8; 32] { + // // TODO improve by replace this with a proper SLIP10 implementation + // match blockchain { + // Blockchain::Bitcoin => + // , + // Blockchain::Monero => , + // } + //} + + pub fn derive_monero_keys(monero_seed: [u8; 32]) -> KeyPair { + let spend = { + let mut bytes = Vec::from(b"farcaster_priv_spend".as_ref()); + bytes.extend_from_slice(&monero_seed); + + let mut key = Hash::hash(&bytes).to_fixed_bytes(); + key[31] &= 0b0000_1111; // Chop off bits that might be greater than the curve modulus + + monero::PrivateKey::from_slice(&key).expect("Modulus is lower than curve25519") + }; + + let view = { + let mut bytes = Vec::from(b"farcaster_priv_view".as_ref()); + bytes.extend_from_slice(&monero_seed); + Hash::hash(&bytes).as_scalar() + }; + + KeyPair { view, spend } } - pub fn new_keyless() -> Self { - Self { seed: None } + //fn derive_bitcoin_keys() -> HashMap Self { + //let master_key = ExtendedPrivKey::new_master(bitcoin::Network::Bitcoin, &seed) + // .map_err(crypto::Error::new)?; + + Self { + seed, + bitcoin_derivations: HashMap::new(), + monero_derivations: Self::derive_monero_keys(seed), + } } pub fn get_btc_privkey(&self, key_id: ArbitratingKeyId) -> Result { let secp = Secp256k1::new(); - if let Some(seed) = self.seed { - let master_key = ExtendedPrivKey::new_master(bitcoin::Network::Bitcoin, &seed) - .map_err(crypto::Error::new)?; - let key = - match key_id { - ArbitratingKeyId::Fund => master_key - .derive_priv(&secp, &DerivationPath::from_str("m/0/1'/1").unwrap()), - ArbitratingKeyId::Buy => master_key - .derive_priv(&secp, &DerivationPath::from_str("m/0/1'/2").unwrap()), - ArbitratingKeyId::Cancel => master_key - .derive_priv(&secp, &DerivationPath::from_str("m/0/1'/3").unwrap()), - ArbitratingKeyId::Refund => master_key - .derive_priv(&secp, &DerivationPath::from_str("m/0/1'/4").unwrap()), - ArbitratingKeyId::Punish => master_key - .derive_priv(&secp, &DerivationPath::from_str("m/0/1'/5").unwrap()), - ArbitratingKeyId::Extra(_) => return Err(crypto::Error::UnsupportedKey), - }; - Ok(key.map_err(crypto::Error::new)?.private_key.key) - } else { - Err(crypto::Error::UnsupportedKey) - } + let seed = self.seed; + let master_key = ExtendedPrivKey::new_master(bitcoin::Network::Bitcoin, &seed) + .map_err(crypto::Error::new)?; + let key = match key_id { + ArbitratingKeyId::Fund => { + master_key.derive_priv(&secp, &DerivationPath::from_str("m/0/1'/1").unwrap()) + } + ArbitratingKeyId::Buy => { + master_key.derive_priv(&secp, &DerivationPath::from_str("m/0/1'/2").unwrap()) + } + ArbitratingKeyId::Cancel => { + master_key.derive_priv(&secp, &DerivationPath::from_str("m/0/1'/3").unwrap()) + } + ArbitratingKeyId::Refund => { + master_key.derive_priv(&secp, &DerivationPath::from_str("m/0/1'/4").unwrap()) + } + ArbitratingKeyId::Punish => { + master_key.derive_priv(&secp, &DerivationPath::from_str("m/0/1'/5").unwrap()) + } + ArbitratingKeyId::Extra(_) => return Err(crypto::Error::UnsupportedKey), + }; + Ok(key.map_err(crypto::Error::new)?.private_key.key) } pub fn get_btc_privkey_by_pub(&self, pubkey: &PublicKey) -> Result { @@ -166,17 +201,13 @@ impl KeyManager { } pub fn private_spend_from_seed(&self) -> Result { - if let Some(seed) = self.seed { - let mut bytes = Vec::from(b"farcaster_priv_spend".as_ref()); - bytes.extend_from_slice(&seed); + let mut bytes = Vec::from(b"farcaster_priv_spend".as_ref()); + bytes.extend_from_slice(&self.seed); - let mut key = Hash::hash(&bytes).to_fixed_bytes(); - key[31] &= 0b0000_1111; // Chop off bits that might be greater than the curve modulus + let mut key = Hash::hash(&bytes).to_fixed_bytes(); + key[31] &= 0b0000_1111; // Chop off bits that might be greater than the curve modulus - monero::PrivateKey::from_slice(&key).map_err(crypto::Error::new) - } else { - Err(crypto::Error::UnsupportedKey) - } + monero::PrivateKey::from_slice(&key).map_err(crypto::Error::new) } } @@ -193,17 +224,13 @@ impl GenerateKey for KeyManager { impl GenerateSharedKey for KeyManager { fn get_shared_key(&self, key_id: SharedKeyId) -> Result { - if let Some(seed) = self.seed { - match key_id.id() { - xmr::SHARED_VIEW_KEY_ID => { - let mut bytes = Vec::from(b"farcaster_priv_view".as_ref()); - bytes.extend_from_slice(&seed); - Ok(Hash::hash(&bytes).as_scalar()) - } - _ => Err(crypto::Error::UnsupportedKey), + match key_id.id() { + xmr::SHARED_VIEW_KEY_ID => { + let mut bytes = Vec::from(b"farcaster_priv_view".as_ref()); + bytes.extend_from_slice(&self.seed); + Ok(Hash::hash(&bytes).as_scalar()) } - } else { - Err(crypto::Error::UnsupportedKey) + _ => Err(crypto::Error::UnsupportedKey), } } } @@ -325,12 +352,6 @@ impl Sign for } } -impl Commit for KeyManager { - fn commit_to>(&self, value: T) -> Hash { - Hash::hash(value.as_ref()) - } -} - impl ProveCrossGroupDleq for KeyManager { /// Generate the proof and the two public keys: the arbitrating public key, also called the /// adaptor public key, and the accordant public spend key. diff --git a/src/swap/mod.rs b/src/swap/mod.rs index 6946e91e..90772fb5 100644 --- a/src/swap/mod.rs +++ b/src/swap/mod.rs @@ -1,11 +1,10 @@ //! Defines the high level of a swap between a Arbitrating blockchain and a Accordant blockchain //! and its concrete instances of swaps. -use std::fmt::Debug; +use std::fmt::{self, Debug}; use std::io; use crate::consensus::{self, CanonicalBytes, Decodable, Encodable}; -use crate::crypto::Commitment; use crate::role::{Accordant, Arbitrating}; use lightning_encoding::strategies::AsStrict; @@ -43,7 +42,7 @@ impl lightning_encoding::Strategy for SwapId { /// Specifie the context of a swap, fixing the arbitrating blockchain, the accordant blockchain and /// the link between them. -pub trait Swap: Debug + Clone + Commitment { +pub trait Swap: Debug + Clone { /// The arbitrating blockchain concrete implementation used for the swap. type Ar: Arbitrating; @@ -52,4 +51,7 @@ pub trait Swap: Debug + Clone + Commitment { ///// The concrete type to link both blockchain cryptographic groups used in by the signatures. type Proof: Clone + Debug + CanonicalBytes; + + /// Commitment type used in the commit/reveal scheme during swap setup. + type Commitment: Clone + PartialEq + Eq + Debug + fmt::Display + CanonicalBytes; }