Skip to content

Commit

Permalink
Merge pull request #242 from bennyhodl/oracle-attestation-validation
Browse files Browse the repository at this point in the history
OracleAttestation validation and follow spec
  • Loading branch information
Tibo-lg authored Oct 31, 2024
2 parents 965b12e + a7252e4 commit 1f3fdae
Show file tree
Hide file tree
Showing 16 changed files with 171 additions and 6 deletions.
20 changes: 14 additions & 6 deletions dlc-manager/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,12 +585,20 @@ where
.iter()
.filter_map(|(i, announcement)| {
let oracle = self.oracles.get(&announcement.oracle_public_key)?;
Some((
*i,
oracle
.get_attestation(&announcement.oracle_event.event_id)
.ok()?,
))
let attestation = oracle
.get_attestation(&announcement.oracle_event.event_id)
.ok()?;
attestation
.validate(&self.secp, announcement)
.map_err(|_| {
log::error!(
"Oracle attestation is not valid. pubkey={} event_id={}",
announcement.oracle_public_key,
announcement.oracle_event.event_id
)
})
.ok()?;
Some((*i, attestation))
})
.collect();
if attestations.len() >= contract_info.threshold {
Expand Down
155 changes: 155 additions & 0 deletions dlc-messages/src/oracle_msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ impl_dlc_writeable!(DigitDecompositionEventDescriptor, {
serde(rename_all = "camelCase")
)]
pub struct OracleAttestation {
/// The identifier of the announcement.
pub event_id: String,
/// The public key of the oracle.
pub oracle_public_key: XOnlyPublicKey,
/// The signatures over the event outcome.
Expand All @@ -322,6 +324,43 @@ pub struct OracleAttestation {
}

impl OracleAttestation {
/// Returns whether the attestation satisfy validity checks.
pub fn validate<C: Verification>(
&self,
secp: &Secp256k1<C>,
announcement: &OracleAnnouncement,
) -> Result<(), Error> {
if self.outcomes.len() != self.signatures.len() {
return Err(Error::InvalidArgument);
}

if self.oracle_public_key != announcement.oracle_public_key {
return Err(Error::InvalidArgument);
}

self.signatures
.iter()
.zip(self.outcomes.iter())
.try_for_each(|(sig, outcome)| {
let hash = bitcoin::hashes::sha256::Hash::hash(outcome.as_bytes());
let msg = Message::from_digest(hash.to_byte_array());
secp.verify_schnorr(sig, &msg, &self.oracle_public_key)
.map_err(|_| Error::InvalidArgument)?;

Ok::<(), dlc::Error>(())
})?;

if !self
.signatures
.iter()
.zip(announcement.oracle_event.oracle_nonces.iter())
.all(|(sig, nonce)| sig.encode()[..32] == nonce.serialize())
{
return Err(Error::InvalidArgument);
}

Ok(())
}
/// Returns the nonces used by the oracle to sign the event outcome.
/// This is used for finding the matching oracle announcement.
pub fn nonces(&self) -> Vec<XOnlyPublicKey> {
Expand All @@ -339,6 +378,7 @@ impl Type for OracleAttestation {
}

impl_dlc_writeable!(OracleAttestation, {
(event_id, string),
(oracle_public_key, {cb_writeable, write_schnorr_pubkey, read_schnorr_pubkey}),
(signatures, {vec_u16_cb, write_schnorrsig, read_schnorrsig}),
(outcomes, {cb_writeable, write_strings_u16, read_strings_u16})
Expand All @@ -347,6 +387,10 @@ impl_dlc_writeable!(OracleAttestation, {
#[cfg(test)]
mod tests {
use super::*;
use bitcoin::bip32::{ChildNumber, Xpriv};
use bitcoin::Network;
use secp256k1_zkp::rand::Fill;
use secp256k1_zkp::SecretKey;
use secp256k1_zkp::{rand::thread_rng, Message, SECP256K1};
use secp256k1_zkp::{schnorr::Signature as SchnorrSignature, Keypair, XOnlyPublicKey};

Expand Down Expand Up @@ -389,6 +433,20 @@ mod tests {
}
}

fn create_nonce_key() -> (SecretKey, XOnlyPublicKey) {
let mut nonce_seed = [0u8; 32];
nonce_seed.try_fill(&mut thread_rng()).unwrap();
let nonce_priv = Xpriv::new_master(Network::Bitcoin, &nonce_seed)
.unwrap()
.derive_priv(SECP256K1, &[ChildNumber::from_normal_idx(1).unwrap()])
.unwrap()
.private_key;

let nonce_xpub = nonce_priv.x_only_public_key(SECP256K1).0;

(nonce_priv, nonce_xpub)
}

#[test]
fn valid_oracle_announcement_passes_validation_test() {
let key_pair = Keypair::new(SECP256K1, &mut thread_rng());
Expand Down Expand Up @@ -462,4 +520,101 @@ mod tests {

assert!(invalid_announcement.validate(SECP256K1).is_err());
}

#[test]
fn valid_oracle_attestation() {
let key_pair = Keypair::new(SECP256K1, &mut thread_rng());
let oracle_pubkey = XOnlyPublicKey::from_keypair(&key_pair).0;
let (nonce_secret, nonce_xpub) = create_nonce_key();

let oracle_event = OracleEvent {
event_id: "test".to_string(),
event_maturity_epoch: 10,
oracle_nonces: vec![nonce_xpub],
event_descriptor: EventDescriptor::EnumEvent(enum_descriptor()),
};

let mut event_hex = Vec::new();
oracle_event
.write(&mut event_hex)
.expect("Error writing oracle event");
let hash = bitcoin::hashes::sha256::Hash::hash(&event_hex);
let msg = Message::from_digest(hash.to_byte_array());
let sig = SECP256K1.sign_schnorr(&msg, &key_pair);

let valid_announcement = OracleAnnouncement {
oracle_public_key: oracle_pubkey,
announcement_signature: sig,
oracle_event,
};

let hash = bitcoin::hashes::sha256::Hash::hash("1".as_bytes());
let msg = Message::from_digest(hash.to_byte_array());
let sig = dlc::secp_utils::schnorrsig_sign_with_nonce(
SECP256K1,
&msg,
&key_pair,
&nonce_secret.secret_bytes(),
);

let attestation = OracleAttestation {
event_id: "test".to_string(),
oracle_public_key: oracle_pubkey,
signatures: vec![sig],
outcomes: vec!["1".to_string()],
};

let validation = attestation.validate(SECP256K1, &valid_announcement);

assert!(validation.is_ok())
}

#[test]
fn invalid_attestation_incorrect_nonce() {
let key_pair = Keypair::new(SECP256K1, &mut thread_rng());
let oracle_pubkey = XOnlyPublicKey::from_keypair(&key_pair).0;
let (_, nonce_xpub) = create_nonce_key();
let (incorrect_nonce_secret, _) = create_nonce_key();

let oracle_event = OracleEvent {
event_id: "test".to_string(),
event_maturity_epoch: 10,
oracle_nonces: vec![nonce_xpub],
event_descriptor: EventDescriptor::EnumEvent(enum_descriptor()),
};

let mut event_hex = Vec::new();
oracle_event
.write(&mut event_hex)
.expect("Error writing oracle event");
let hash = bitcoin::hashes::sha256::Hash::hash(&event_hex);
let msg = Message::from_digest(hash.to_byte_array());
let sig = SECP256K1.sign_schnorr(&msg, &key_pair);

let valid_announcement = OracleAnnouncement {
oracle_public_key: oracle_pubkey,
announcement_signature: sig,
oracle_event,
};

let hash = bitcoin::hashes::sha256::Hash::hash("1".as_bytes());
let msg = Message::from_digest(hash.to_byte_array());
let sig = dlc::secp_utils::schnorrsig_sign_with_nonce(
SECP256K1,
&msg,
&key_pair,
&incorrect_nonce_secret.secret_bytes(),
);

let attestation = OracleAttestation {
event_id: "test".to_string(),
oracle_public_key: oracle_pubkey,
signatures: vec![sig],
outcomes: vec!["1".to_string()],
};

let validation = attestation.validate(SECP256K1, &valid_announcement);

assert!(validation.is_err())
}
}
Binary file modified dlc-sled-storage-provider/test_files/Accepted
Binary file not shown.
Binary file modified dlc-sled-storage-provider/test_files/AcceptedChannel
Binary file not shown.
Binary file modified dlc-sled-storage-provider/test_files/Closed
Binary file not shown.
Binary file modified dlc-sled-storage-provider/test_files/Confirmed
Binary file not shown.
Binary file modified dlc-sled-storage-provider/test_files/Confirmed1
Binary file not shown.
Binary file modified dlc-sled-storage-provider/test_files/Offered
Binary file not shown.
Binary file modified dlc-sled-storage-provider/test_files/OfferedChannel
Binary file not shown.
Binary file modified dlc-sled-storage-provider/test_files/PreClosed
Binary file not shown.
Binary file modified dlc-sled-storage-provider/test_files/Signed
Binary file not shown.
Binary file modified dlc-sled-storage-provider/test_files/Signed1
Binary file not shown.
Binary file modified dlc-sled-storage-provider/test_files/SignedChannelEstablished
Binary file not shown.
Binary file modified dlc-sled-storage-provider/test_files/SignedChannelSettled
Binary file not shown.
1 change: 1 addition & 0 deletions mocks/src/mock_oracle_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ impl MockOracle {
})
.collect();
let attestation = OracleAttestation {
event_id: event_id.to_string(),
oracle_public_key: self.get_public_key(),
signatures,
outcomes: outcomes.to_vec(),
Expand Down
1 change: 1 addition & 0 deletions p2pd-oracle-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ impl Oracle for P2PDOracleClient {
} = get::<AttestationResponse>(&path)?;

Ok(OracleAttestation {
event_id: event_id.to_string(),
oracle_public_key: self.public_key,
signatures,
outcomes: values,
Expand Down

0 comments on commit 1f3fdae

Please sign in to comment.