diff --git a/.gitignore b/.gitignore index f44c948..98b3a66 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ *.env *.env.local .env -.env.local \ No newline at end of file +.env.local +.idea +.vscode diff --git a/kormir-server/src/main.rs b/kormir-server/src/main.rs index 19d9541..7c06516 100644 --- a/kormir-server/src/main.rs +++ b/kormir-server/src/main.rs @@ -101,6 +101,8 @@ async fn main() -> anyhow::Result<()> { .route("/attestation/:event_id", get(get_oracle_attestation)) .route("/create-enum", post(create_enum_event)) .route("/sign-enum", post(sign_enum_event)) + .route("/create-numeric", post(create_numeric_event)) + .route("/sign-numeric", post(sign_numeric_event)) .fallback(fallback) .layer(Extension(state)); diff --git a/kormir-server/src/models/mod.rs b/kormir-server/src/models/mod.rs index 8ab3214..9d363ab 100644 --- a/kormir-server/src/models/mod.rs +++ b/kormir-server/src/models/mod.rs @@ -11,7 +11,6 @@ use kormir::error::Error; use kormir::lightning::util::ser::Writeable; use kormir::storage::{OracleEventData, Storage}; use nostr::EventId; -use std::collections::HashMap; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; @@ -216,7 +215,7 @@ impl Storage for PostgresStorage { async fn save_signatures( &self, id: u32, - signatures: HashMap, + signatures: Vec<(String, Signature)>, ) -> Result { let id = id as i32; let mut conn = self.db_pool.get().map_err(|_| Error::StorageFailure)?; diff --git a/kormir-server/src/routes.rs b/kormir-server/src/routes.rs index b18bb13..8a89094 100644 --- a/kormir-server/src/routes.rs +++ b/kormir-server/src/routes.rs @@ -1,13 +1,17 @@ use crate::State; use axum::extract::Path; +use axum::extract::Query; use axum::http::StatusCode; use axum::{Extension, Json}; use bitcoin::key::XOnlyPublicKey; +use dlc_messages::ser_impls::write_as_tlv; use kormir::lightning::util::ser::Writeable; use kormir::storage::{OracleEventData, Storage}; use kormir::{OracleAnnouncement, OracleAttestation, Signature}; use nostr::{EventId, JsonUtil}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; use std::time::SystemTime; pub async fn health_check() -> Result, (StatusCode, String)> { @@ -21,15 +25,29 @@ pub async fn get_pubkey( } pub async fn list_events( + Query(params): Query>, Extension(state): Extension, -) -> Result>, (StatusCode, String)> { +) -> Result, (StatusCode, String)> { let events = state.oracle.storage.list_events().await.map_err(|_| { ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to list events".to_string(), ) })?; - Ok(Json(events)) + + if let Some(format) = params.get("format") { + if format == "json" { + Ok(list_events_json(&events)) + } else if format == "hex" { + Ok(list_events_hex(&events)) + } else if format == "tlv" { + Ok(list_events_tlv(&events)) + } else { + Err((StatusCode::BAD_REQUEST, "Invalid format".into())) + } + } else { + Ok(list_events_json(&events)) + } } #[derive(Debug, Clone, Deserialize)] @@ -164,6 +182,94 @@ pub async fn sign_enum_event( } } +#[derive(Debug, Clone, Deserialize)] +pub struct CreateNumericEvent { + pub event_id: String, + pub num_digits: Option, + pub is_signed: Option, + pub precision: Option, + pub unit: String, + pub event_maturity_epoch: u32, +} + +async fn create_numeric_event_impl( + state: &State, + body: crate::routes::CreateNumericEvent, +) -> anyhow::Result { + let (id, ann) = state + .oracle + .create_numeric_event( + body.event_id, + body.num_digits.unwrap_or(18), + body.is_signed.unwrap_or(false), + body.precision.unwrap_or(0), + body.unit, + body.event_maturity_epoch, + ) + .await?; + let hex = hex::encode(ann.encode()); + + log::info!("Created numeric event: {hex}"); + + let relays = state + .client + .relays() + .await + .keys() + .map(|x| x.to_string()) + .collect::>(); + + let event = + kormir::nostr_events::create_announcement_event(&state.oracle.nostr_keys(), &ann, &relays)?; + + log::debug!("Broadcasting nostr event: {}", event.as_json()); + + state + .oracle + .storage + .add_announcement_event_id(id, event.id) + .await?; + + log::debug!( + "Added announcement event id to storage: {}", + event.id.to_hex() + ); + + state.client.send_event(event).await?; + + Ok(hex) +} + +pub async fn create_numeric_event( + Extension(state): Extension, + Json(body): Json, +) -> Result, (StatusCode, String)> { + if body.num_digits.is_some() && body.num_digits.unwrap() == 0 { + return Err(( + StatusCode::BAD_REQUEST, + "Number of digits must be greater than 0".to_string(), + )); + } + + if body.event_maturity_epoch < now() { + return Err(( + StatusCode::BAD_REQUEST, + "Event maturity epoch must be in the future".to_string(), + )); + } + + match crate::routes::create_numeric_event_impl(&state, body).await { + Ok(hex) => Ok(Json(hex)), + Err(e) => { + eprintln!("Error creating numeric event: {:?}", e); + Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Error creating numeric event".to_string(), + )) + } + } +} + pub fn get_oracle_announcement_impl( state: &State, event_id: String, @@ -244,9 +350,139 @@ pub async fn get_oracle_attestation( } } +#[derive(Debug, Clone, Deserialize)] +pub struct SignNumericEvent { + pub id: u32, + pub outcome: i64, +} + +async fn sign_numeric_event_impl( + state: &State, + body: crate::routes::SignNumericEvent, +) -> anyhow::Result { + let att = state + .oracle + .sign_numeric_event(body.id, body.outcome) + .await?; + let hex = hex::encode(att.encode()); + + log::info!("Signed numeric event: {hex}"); + + let data = state.oracle.storage.get_event(body.id).await?; + let event_id = data + .and_then(|d| { + d.announcement_event_id + .and_then(|s| EventId::from_hex(s).ok()) + }) + .ok_or_else(|| anyhow::anyhow!("Failed to get announcement event id"))?; + + let event = + kormir::nostr_events::create_attestation_event(&state.oracle.nostr_keys(), &att, event_id)?; + + log::debug!("Broadcasting nostr event: {}", event.as_json()); + + state + .oracle + .storage + .add_attestation_event_id(body.id, event.id) + .await?; + + log::debug!( + "Added announcement event id to storage: {}", + event.id.to_hex() + ); + + state.client.send_event(event).await?; + + Ok(hex) +} + +pub async fn sign_numeric_event( + Extension(state): Extension, + Json(body): Json, +) -> Result, (StatusCode, String)> { + match crate::routes::sign_numeric_event_impl(&state, body).await { + Ok(hex) => Ok(Json(hex)), + Err(e) => { + eprintln!("Error signing numeric event: {:?}", e); + Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Error signing numeric event".to_string(), + )) + } + } +} + fn now() -> u32 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs() as u32 } + +fn list_events_json(events: &Vec) -> Json { + Json(serde_json::to_value(events).unwrap()) +} + +#[derive(Debug, Clone, Serialize)] +struct HexEvent { + pub id: Option, + pub event_id: String, + pub event_maturity_epoch: u32, + pub announcement: String, + pub attestation: Option, +} + +fn list_events_hex(events: &[OracleEventData]) -> Json { + let hex_events = events + .iter() + .map(|e| { + let attestation = assemble_attestation(e); + HexEvent { + id: e.id, + event_id: e.announcement.oracle_event.event_id.clone(), + event_maturity_epoch: e.announcement.oracle_event.event_maturity_epoch, + announcement: hex::encode(e.announcement.encode()), + attestation: attestation.map(|a| hex::encode(a.encode())), + } + }) + .collect::>(); + Json(serde_json::to_value(hex_events).unwrap()) +} + +fn list_events_tlv(events: &[OracleEventData]) -> Json { + let tlv_events = events + .iter() + .map(|e| { + let attestation = assemble_attestation(e); + HexEvent { + id: e.id, + event_id: e.announcement.oracle_event.event_id.clone(), + event_maturity_epoch: e.announcement.oracle_event.event_maturity_epoch, + announcement: { + let mut bytes = Vec::new(); + write_as_tlv(&e.announcement, &mut bytes).unwrap(); + hex::encode(bytes) + }, + attestation: attestation.map(|a| { + let mut bytes = Vec::new(); + write_as_tlv(&a, &mut bytes).unwrap(); + hex::encode(bytes) + }), + } + }) + .collect::>(); + Json(serde_json::to_value(tlv_events).unwrap()) +} + +fn assemble_attestation(e: &OracleEventData) -> Option { + if e.signatures.is_empty() { + None + } else { + Some(OracleAttestation { + oracle_public_key: e.announcement.oracle_public_key, + signatures: e.signatures.iter().map(|x| x.1).collect(), + outcomes: e.signatures.iter().map(|x| x.0.clone()).collect(), + }) + } +} diff --git a/kormir-wasm/src/lib.rs b/kormir-wasm/src/lib.rs index 2581d9b..cd229ce 100644 --- a/kormir-wasm/src/lib.rs +++ b/kormir-wasm/src/lib.rs @@ -142,6 +142,76 @@ impl Kormir { Ok(hex::encode(attestation.encode())) } + pub async fn create_numeric_event( + &self, + event_id: String, + num_digits: u16, + is_signed: bool, + precision: i32, + unit: String, + event_maturity_epoch: u32, + ) -> Result { + let (id, ann) = self + .oracle + .create_numeric_event( + event_id, + num_digits, + is_signed, + precision, + unit, + event_maturity_epoch, + ) + .await?; + + let hex = hex::encode(ann.encode()); + + log::info!("Created numeric event: {hex}"); + + let event = kormir::nostr_events::create_announcement_event( + &self.oracle.nostr_keys(), + &ann, + &self.relays, + )?; + + log::debug!("Created nostr event: {}", event.as_json()); + + self.storage + .add_announcement_event_id(id, event.id.to_hex()) + .await?; + + log::debug!( + "Added announcement event id to storage: {}", + event.id.to_hex() + ); + + self.client.send_event(event).await?; + + log::trace!("Sent event to nostr"); + + Ok(hex) + } + + pub async fn sign_numeric_event(&self, id: u32, outcome: i64) -> Result { + let attestation = self.oracle.sign_numeric_event(id, outcome).await?; + + let event = self.storage.get_event(id).await?.ok_or(JsError::NotFound)?; + let event_id = EventId::from_hex(event.announcement_event_id.unwrap()).unwrap(); + + let event = kormir::nostr_events::create_attestation_event( + &self.oracle.nostr_keys(), + &attestation, + event_id, + )?; + + self.storage + .add_attestation_event_id(id, event.id.to_hex()) + .await?; + + self.client.send_event(event).await?; + + Ok(hex::encode(attestation.encode())) + } + pub async fn list_events(&self) -> Result */, JsError> { let data = self.storage.list_events().await?; let events = data.into_iter().map(EventData::from).collect::>(); diff --git a/kormir-wasm/src/models.rs b/kormir-wasm/src/models.rs index 133b95c..ecec8cf 100644 --- a/kormir-wasm/src/models.rs +++ b/kormir-wasm/src/models.rs @@ -181,7 +181,7 @@ impl From<(u32, OracleEventData)> for EventData { let outcomes = match &value.announcement.oracle_event.event_descriptor { EventDescriptor::EnumEvent(e) => e.outcomes.clone(), EventDescriptor::DigitDecompositionEvent(_) => { - unimplemented!("Numeric events not supported") + vec![] } }; @@ -191,11 +191,28 @@ impl From<(u32, OracleEventData)> for EventData { // todo proper sorting for non-enum events let attestation = OracleAttestation { oracle_public_key: value.announcement.oracle_public_key, - signatures: value.signatures.values().cloned().collect(), - outcomes: value.signatures.keys().cloned().collect(), + signatures: value.signatures.iter().map(|x| x.1).collect(), + outcomes: value.signatures.iter().map(|x| x.0.clone()).collect(), }; let attestation = hex::encode(attestation.encode()); - let outcome = value.signatures.keys().next().cloned().unwrap(); + let outcome = match &value.announcement.oracle_event.event_descriptor { + EventDescriptor::EnumEvent(_) => { + value.signatures.iter().map(|x| x.0.clone()).next().unwrap() + } + EventDescriptor::DigitDecompositionEvent(_) => { + let mut outcome_str = value + .signatures + .iter() + .map(|x| x.0.clone()) + .collect::>() + .join(""); + if outcome_str.starts_with('+') { + outcome_str.remove(0); + } + let outcome = i64::from_str_radix(&outcome_str, 2).unwrap(); + outcome.to_string() + } + }; (Some(attestation), Some(outcome)) } }; diff --git a/kormir-wasm/src/storage.rs b/kormir-wasm/src/storage.rs index 7a7c548..16a2cd7 100644 --- a/kormir-wasm/src/storage.rs +++ b/kormir-wasm/src/storage.rs @@ -5,7 +5,6 @@ use kormir::storage::{OracleEventData, Storage}; use kormir::{OracleAnnouncement, Signature}; use rexie::{ObjectStore, Rexie, TransactionMode}; use serde::Serialize; -use std::collections::HashMap; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; use wasm_bindgen::JsValue; @@ -194,7 +193,7 @@ impl Storage for IndexedDb { async fn save_signatures( &self, id: u32, - sigs: HashMap, + sigs: Vec<(String, Signature)>, ) -> Result { let mut event = self.get_event(id).await?.ok_or(Error::NotFound)?; if !event.signatures.is_empty() { diff --git a/kormir/src/lib.rs b/kormir/src/lib.rs index da19339..d6cd38c 100644 --- a/kormir/src/lib.rs +++ b/kormir/src/lib.rs @@ -13,11 +13,11 @@ use bitcoin::key::XOnlyPublicKey; use bitcoin::secp256k1::{All, Message, Secp256k1, SecretKey}; use bitcoin::Network; use secp256k1_zkp::Keypair; -use std::collections::HashMap; use std::str::FromStr; pub use bitcoin; pub use bitcoin::secp256k1::schnorr::Signature; +use dlc_messages::oracle_msgs::DigitDecompositionEventDescriptor; pub use dlc_messages::oracle_msgs::{ EnumEventDescriptor, EventDescriptor, OracleAnnouncement, OracleAttestation, OracleEvent, }; @@ -183,8 +183,7 @@ impl Oracle { return Err(Error::Internal); }; - let mut sigs = HashMap::with_capacity(1); - sigs.insert(outcome.clone(), sig); + let sigs = vec![(outcome.clone(), sig)]; self.storage.save_signatures(id, sigs).await?; @@ -196,6 +195,163 @@ impl Oracle { Ok(attestation) } + + pub async fn create_numeric_event( + &self, + event_id: String, + num_digits: u16, + is_signed: bool, + precision: i32, + unit: String, + event_maturity_epoch: u32, + ) -> Result<(u32, OracleAnnouncement), Error> { + if num_digits == 0 { + return Err(Error::InvalidArgument); + } + + let num_nonces = if is_signed { + num_digits as usize + 1 + } else { + num_digits as usize + }; + + let indexes = self.storage.get_next_nonce_indexes(num_nonces).await?; + let oracle_nonces = indexes + .iter() + .map(|i| { + let nonce_key = self.get_nonce_key(*i); + nonce_key.x_only_public_key(&self.secp).0 + }) + .collect(); + let event_descriptor = + EventDescriptor::DigitDecompositionEvent(DigitDecompositionEventDescriptor { + base: 2, + is_signed, + unit, + precision, + nb_digits: num_digits, + }); + let oracle_event = OracleEvent { + oracle_nonces, + event_id, + event_maturity_epoch, + event_descriptor, + }; + oracle_event.validate().map_err(|_| Error::Internal)?; + + // create signature + let mut data = Vec::new(); + oracle_event.write(&mut data).map_err(|_| Error::Internal)?; + let hash = sha256::Hash::hash(&data); + let msg = Message::from_digest(hash.to_byte_array()); + let announcement_signature = self.secp.sign_schnorr_no_aux_rand(&msg, &self.key_pair); + + let ann = OracleAnnouncement { + oracle_event, + oracle_public_key: self.public_key(), + announcement_signature, + }; + ann.validate(&self.secp).map_err(|_| Error::Internal)?; + + let id = self.storage.save_announcement(ann.clone(), indexes).await?; + + Ok((id, ann)) + } + + pub async fn sign_numeric_event( + &self, + id: u32, + outcome: i64, + ) -> Result { + let Some(data) = self.storage.get_event(id).await? else { + return Err(Error::NotFound); + }; + if !data.signatures.is_empty() { + return Err(Error::EventAlreadySigned); + } + let descriptor = match &data.announcement.oracle_event.event_descriptor { + EventDescriptor::DigitDecompositionEvent(desc) => desc, + _ => return Err(Error::Internal), + }; + if descriptor.base != 2 { + return Err(Error::Internal); + } + let max_value = (descriptor.base as i64).pow(descriptor.nb_digits as u32) - 1; + let min_value = if descriptor.is_signed { -max_value } else { 0 }; + if outcome < min_value || outcome > max_value { + return Err(Error::InvalidOutcome); + } + + let digits = format!( + "{:0width$b}", + outcome.abs(), + width = descriptor.nb_digits as usize + ) + .chars() + .map(|char| char.to_string()) + .collect::>(); + + let outcomes = if descriptor.is_signed { + let mut sign = vec![if outcome < 0 { + "-".to_string() + } else { + "+".to_string() + }]; + sign.extend(digits); + sign + } else { + digits + }; + + if data.indexes.len() != outcomes.len() { + return Err(Error::Internal); + } + + let nonce_keys = data.indexes.iter().map(|i| self.get_nonce_key(*i)); + + let mut sigs: Vec<(String, Signature)> = vec![]; + + let signatures = outcomes + .iter() + .zip(nonce_keys) + .enumerate() + .map(|(idx, (outcome, nonce_key))| { + let hash = sha256::Hash::hash(outcome.as_bytes()); + let msg = Message::from_digest(hash.to_byte_array()); + let sig = dlc::secp_utils::schnorrsig_sign_with_nonce( + &self.secp, + &msg, + &self.key_pair, + &nonce_key.secret_bytes(), + ); + // verify our nonce is the same as the one in the announcement + debug_assert!( + sig.encode()[..32] + == data.announcement.oracle_event.oracle_nonces[idx].serialize() + ); + // verify our signature + if self + .secp + .verify_schnorr(&sig, &msg, &self.key_pair.x_only_public_key().0) + .is_err() + { + return Err(Error::Internal); + }; + sigs.push((outcome.clone(), sig)); + Ok(sig) + }) + .collect::, Error>>()?; + + self.storage.save_signatures(id, sigs).await?; + + let attestation = OracleAttestation { + oracle_public_key: self.public_key(), + signatures, + outcomes, + }; + + Ok(attestation) + } } pub fn derive_signing_key(secp: &Secp256k1, xpriv: Xpriv) -> Result { @@ -277,4 +433,228 @@ mod test { assert_eq!(rx, expected_nonce) } + + #[tokio::test] + async fn test_create_unsigned_numeric_event() { + let oracle = create_oracle(); + + let event_id = "test_unsigned_numeric".to_string(); + let num_digits = 20; + + let event_maturity_epoch = 100; + let (_, ann) = oracle + .create_numeric_event( + event_id.clone(), + num_digits, + false, + 0, + "m/s".into(), + event_maturity_epoch, + ) + .await + .unwrap(); + + assert!(ann.validate(&oracle.secp).is_ok()); + assert_eq!(ann.oracle_event.event_id, event_id); + assert_eq!(ann.oracle_event.event_maturity_epoch, event_maturity_epoch); + assert_eq!( + ann.oracle_event.event_descriptor, + EventDescriptor::DigitDecompositionEvent(DigitDecompositionEventDescriptor { + base: 2, + is_signed: false, + unit: "m/s".into(), + precision: 0, + nb_digits: 20, + }) + ); + } + + #[tokio::test] + async fn test_sign_unsigned_numeric_event() { + let oracle = create_oracle(); + + let event_id = "test_unsigned_numeric".to_string(); + let num_digits = 16; + + let event_maturity_epoch = 100; + let (id, ann) = oracle + .create_numeric_event( + event_id.clone(), + num_digits, + false, + 0, + "m/s".into(), + event_maturity_epoch, + ) + .await + .unwrap(); + + println!("{}", hex::encode(ann.encode())); + let res = oracle.sign_numeric_event(id, 0x55555).await; + assert!(res.is_err()); + let attestation = oracle.sign_numeric_event(id, 0x5555).await.unwrap(); + assert_eq!( + attestation.outcomes, + vec!["0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1"] + .iter() + .map(|x| x.to_string()) + .collect::>() + ); + assert_eq!(attestation.oracle_public_key, oracle.public_key()); + assert_eq!(attestation.signatures.len(), 16); + assert_eq!(attestation.outcomes.len(), 16); + + for i in 0..attestation.signatures.len() { + let sig = attestation.signatures[i]; + + // check first 32 bytes of signature is expected nonce + let expected_nonce = ann.oracle_event.oracle_nonces[i].serialize(); + let bytes = sig.encode(); + let (rx, _sig) = bytes.split_at(32); + + assert_eq!(rx, expected_nonce) + } + + println!("{}", hex::encode(attestation.encode())); + } + + #[ignore] + #[tokio::test] + async fn test_create_signed_numeric_event() { + let oracle = create_oracle(); + + let event_id = "test_signed_numeric".to_string(); + let num_digits = 20; + + let event_maturity_epoch = 100; + let (_, ann) = oracle + .create_numeric_event( + event_id.clone(), + num_digits, + true, + 0, + "m/s".into(), + event_maturity_epoch, + ) + .await + .unwrap(); + + assert!(ann.validate(&oracle.secp).is_ok()); + assert_eq!(ann.oracle_event.event_id, event_id); + assert_eq!(ann.oracle_event.event_maturity_epoch, event_maturity_epoch); + assert_eq!( + ann.oracle_event.event_descriptor, + EventDescriptor::DigitDecompositionEvent(DigitDecompositionEventDescriptor { + base: 2, + is_signed: true, + unit: "m/s".into(), + precision: 0, + nb_digits: 20, + }) + ); + } + + #[ignore] + #[tokio::test] + async fn test_sign_signed_positive_numeric_event() { + let oracle = create_oracle(); + + let event_id = "test_signed_numeric".to_string(); + let num_digits = 16; + + let event_maturity_epoch = 100; + let (id, ann) = oracle + .create_numeric_event( + event_id.clone(), + num_digits, + true, + 0, + "m/s".into(), + event_maturity_epoch, + ) + .await + .unwrap(); + + println!("{}", hex::encode(ann.encode())); + let res = oracle.sign_numeric_event(id, 0x55555).await; + assert!(res.is_err()); + let attestation = oracle.sign_numeric_event(id, 0x5555).await.unwrap(); + assert_eq!( + attestation.outcomes, + vec![ + "+", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1" + ] + .iter() + .map(|x| x.to_string()) + .collect::>() + ); + assert_eq!(attestation.oracle_public_key, oracle.public_key()); + assert_eq!(attestation.signatures.len(), 16 + 1); + assert_eq!(attestation.outcomes.len(), 16 + 1); + + for i in 0..attestation.signatures.len() { + let sig = attestation.signatures[i]; + + // check first 32 bytes of signature is expected nonce + let expected_nonce = ann.oracle_event.oracle_nonces[i].serialize(); + let bytes = sig.encode(); + let (rx, _sig) = bytes.split_at(32); + + assert_eq!(rx, expected_nonce) + } + + println!("{}", hex::encode(attestation.encode())); + } + + #[ignore] + #[tokio::test] + async fn test_sign_signed_negative_numeric_event() { + let oracle = create_oracle(); + + let event_id = "test_signed_numeric".to_string(); + let num_digits = 16; + + let event_maturity_epoch = 100; + let (id, ann) = oracle + .create_numeric_event( + event_id.clone(), + num_digits, + true, + 0, + "m/s".into(), + event_maturity_epoch, + ) + .await + .unwrap(); + + println!("{}", hex::encode(ann.encode())); + let res = oracle.sign_numeric_event(id, -0x55555).await; + assert!(res.is_err()); + let attestation = oracle.sign_numeric_event(id, -0x5555).await.unwrap(); + assert_eq!( + attestation.outcomes, + vec![ + "-", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1" + ] + .iter() + .map(|x| x.to_string()) + .collect::>() + ); + assert_eq!(attestation.oracle_public_key, oracle.public_key()); + assert_eq!(attestation.signatures.len(), 16 + 1); + assert_eq!(attestation.outcomes.len(), 16 + 1); + + for i in 0..attestation.signatures.len() { + let sig = attestation.signatures[i]; + + // check first 32 bytes of signature is expected nonce + let expected_nonce = ann.oracle_event.oracle_nonces[i].serialize(); + let bytes = sig.encode(); + let (rx, _sig) = bytes.split_at(32); + + assert_eq!(rx, expected_nonce) + } + + println!("{}", hex::encode(attestation.encode())); + } } diff --git a/kormir/src/storage.rs b/kormir/src/storage.rs index 5067121..5aa719d 100644 --- a/kormir/src/storage.rs +++ b/kormir/src/storage.rs @@ -23,7 +23,7 @@ pub trait Storage { async fn save_signatures( &self, id: u32, - sigs: HashMap, + sigs: Vec<(String, Signature)>, ) -> Result; /// Get the announcement data for the given id @@ -36,7 +36,7 @@ pub struct OracleEventData { pub id: Option, pub announcement: OracleAnnouncement, pub indexes: Vec, - pub signatures: HashMap, + pub signatures: Vec<(String, Signature)>, #[cfg(feature = "nostr")] pub announcement_event_id: Option, #[cfg(feature = "nostr")] @@ -102,7 +102,7 @@ impl Storage for MemoryStorage { async fn save_signatures( &self, id: u32, - sigs: HashMap, + sigs: Vec<(String, Signature)>, ) -> Result { let mut data = self.data.try_write().unwrap(); let Some(mut event) = data.get(&id).cloned() else {