All files / lib/messages OracleAnnouncement.ts

93.02% Statements 40/43
56.25% Branches 9/16
88.89% Functions 8/9
93.02% Lines 40/43

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 1771x 1x   1x 1x   1x                                 1x 1x               163x     163x           163x       163x       163x               253x 253x   253x 253x 253x 253x 253x   253x           471x                                       13x     12x         12x             12x 12x       12x   1x                 3x                           16x             86x                     680x 680x   680x 680x 680x 680x   680x 680x   680x                    
import { BufferReader, BufferWriter } from '@node-dlc/bufio';
import { math, verify } from 'bip-schnorr';
 
import { MessageType } from '../MessageType';
import { getTlv } from '../serialize/getTlv';
import { IDlcMessage } from './DlcMessage';
import { IOracleEventJSON, OracleEvent } from './OracleEvent';
 
/**
 * Oracle announcement that describe an event and the way that an oracle will
 * attest to it. Updated to be rust-dlc compliant.
 *
 * In order to make it possible to hold oracles accountable in cases where
 * they do not release a signature for an event outcome, there needs to be
 * a proof that an oracle has committed to a given outcome. This proof is
 * given in a so-called oracle announcement, which contains an oracle event
 * together with the oracle public key and a signature over its serialization,
 * which must be valid with respect to the specified public key.
 *
 * This also makes it possible for users to obtain oracle event information
 * from an un-trusted peer while being guaranteed that it originates from a
 * given oracle.
 */
export class OracleAnnouncement implements IDlcMessage {
  public static type = MessageType.OracleAnnouncement;
 
  /**
   * Creates an OracleAnnouncement from JSON data
   * @param json JSON object representing oracle announcement
   */
  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
  public static fromJSON(json: any): OracleAnnouncement {
    const instance = new OracleAnnouncement();
 
    // Handle different field name variations
    instance.announcementSig = Buffer.from(
      json.announcementSignature ||
        json.announcementSig ||
        json.announcement_signature,
      'hex',
    );
    instance.oraclePubkey = Buffer.from(
      json.oraclePublicKey || json.oraclePubkey || json.oracle_public_key,
      'hex',
    );
    instance.oracleEvent = OracleEvent.fromJSON(
      json.oracleEvent || json.oracle_event,
    );
 
    return instance;
  }
 
  /**
   * Deserializes an oracle_announcement message
   * @param buf
   */
  public static deserialize(buf: Buffer): OracleAnnouncement {
    const instance = new OracleAnnouncement();
    const reader = new BufferReader(buf);
 
    reader.readBigSize(); // read type
    instance.length = reader.readBigSize();
    instance.announcementSig = reader.readBytes(64);
    instance.oraclePubkey = reader.readBytes(32);
    instance.oracleEvent = OracleEvent.deserialize(getTlv(reader));
 
    return instance;
  }
 
  /**
   * The type for oracle_announcement message. oracle_announcement = 55332
   */
  public type = OracleAnnouncement.type;
 
  public length: bigint;
 
  /** The signature enabling verifying the origin of the announcement. */
  public announcementSig: Buffer;
 
  /** The public key of the oracle (32 bytes, x-only). */
  public oraclePubkey: Buffer;
 
  /** The description of the event and attesting. */
  public oracleEvent: OracleEvent;
 
  /**
   * Validates the oracle announcement according to rust-dlc specification.
   * This includes validating the oracle event and verifying the announcement signature.
   * @throws Will throw an error if validation fails
   */
  public validate(): void {
    // Validate oracle event first
    this.oracleEvent.validate();
 
    // Validate oracle public key format (32 bytes for x-only)
    Iif (!this.oraclePubkey || this.oraclePubkey.length !== 32) {
      throw new Error('Oracle public key must be 32 bytes (x-only format)');
    }
 
    // Validate announcement signature format (64 bytes for Schnorr)
    Iif (!this.announcementSig || this.announcementSig.length !== 64) {
      throw new Error(
        'Announcement signature must be 64 bytes (Schnorr format)',
      );
    }
 
    // Verify announcement signature over the oracle event
    try {
      const msg = math.taggedHash(
        'DLC/oracle/announcement/v0',
        this.oracleEvent.serialize(),
      );
      verify(this.oraclePubkey, msg, this.announcementSig);
    } catch (error) {
      throw new Error(`Invalid announcement signature: ${error.message}`);
    }
  }
 
  /**
   * Returns the nonces from the oracle event.
   * This is useful for finding matching oracle announcements.
   */
  public getNonces(): Buffer[] {
    return this.oracleEvent.oracleNonces;
  }
 
  /**
   * Returns the event maturity epoch from the oracle event.
   */
  public getEventMaturityEpoch(): number {
    return this.oracleEvent.eventMaturityEpoch;
  }
 
  /**
   * Returns the event ID from the oracle event.
   */
  public getEventId(): string {
    return this.oracleEvent.eventId;
  }
 
  /**
   * Converts oracle_announcement to JSON (canonical rust-dlc format)
   */
  public toJSON(): OracleAnnouncementJSON {
    return {
      announcementSignature: this.announcementSig.toString('hex'),
      oraclePublicKey: this.oraclePubkey.toString('hex'),
      oracleEvent: this.oracleEvent.toJSON(),
    };
  }
 
  /**
   * Serializes the oracle_announcement message into a Buffer
   */
  public serialize(): Buffer {
    const writer = new BufferWriter();
    writer.writeBigSize(this.type);
 
    const dataWriter = new BufferWriter();
    dataWriter.writeBytes(this.announcementSig);
    dataWriter.writeBytes(this.oraclePubkey);
    dataWriter.writeBytes(this.oracleEvent.serialize());
 
    writer.writeBigSize(dataWriter.size);
    writer.writeBytes(dataWriter.toBuffer());
 
    return writer.toBuffer();
  }
}
 
export interface OracleAnnouncementJSON {
  type?: number; // Made optional for rust-dlc compatibility
  announcementSignature: string; // Canonical field name
  oraclePublicKey: string; // Canonical field name
  oracleEvent: IOracleEventJSON;
}