codenlighten/scrypt
0
1---2sidebar_position: 33---4 5# Tutorial 3: Oracle6 7## Overview8 9In this tutorial, we will go over how to build a smart contract that consumes off-chain data from an oracle. Specifically, we will implement a smart contract that lets two players bet on the price of BSV at some point in the future. It retrieves prices from an oracle.10 11### What is an Oracle?12A blockchain oracle is a third-party service or agent that provides external data to a blockchain network. It is a bridge between the blockchain and the external world, enabling smart contracts to access, verify, and incorporate data from outside the blockchain. This allows smart contracts to execute based on real-world events and conditions, enhancing their utility and functionality.13 14 15 16[Credit: bitnovo](https://blog.bitnovo.com/en/what-is-a-blockchain-oracle/)17 18The data supplied by oracles can include various types of information, such as stock prices, weather data, election results, and sports scores.19 20### Rabin Signatures21A digital signature is required to verify the authenticity and integrity of arbitrary data provided by known oracles in a smart contract. Instead of ECDSA used in Bitcoin, we use an alternative digital signature algorithm called [Rabin signatures](https://en.wikipedia.org/wiki/Rabin_signature_algorithm). This is because Rabin signature verification is orders of magnitude cheaper than ECDSA.22We have implemented [Rabin signature](https://github.com/sCrypt-Inc/scrypt-ts-lib/blob/master/src/rabinSignature.ts) as part of the standard libraries [`scrypt-ts-lib`](https://www.npmjs.com/package/scrypt-ts-lib), which can be imported and used directly. 23 24## Contract Properties25 26Our contract will take signed pricing data from the [WitnessOnChain oracle](https://witnessonchain.com). Depending if the price target is reached or not, it will pay out a reward to one of the two players.27 28There are quite a few properties which our price betting smart contract will require:29 30```ts31// Price target that needs to be reached.32@prop()33targetPrice: bigint34 35// Symbol of the pair, e.g. "BSV_USDC"36@prop()37symbol: ByteString38 39// Timestamp window in which the price target needs to be reached.40@prop()41timestampFrom: bigint42@prop()43timestampTo: bigint44 45// Oracles Rabin public key.46@prop()47oraclePubKey: RabinPubKey48 49// Addresses of both players.50@prop()51alicePkh: PubKeyHash52@prop()53bobPkh: PubKeyHash54```55 56Notice that the type `RabinPubKey`, which represents a Rabin public key, is not a standard type. You can import it the following way:57 58```ts59import { RabinPubKey } from 'scrypt-ts-lib'60```61 62## Public Method - `unlock`63 64The contract will have only a single public method, namely `unlock`. As parameters, it will take the oracles signature, the signed message from the oracle, and a signature of the winner, who can unlock the funds:65 66```ts67@method()68public unlock(msg: ByteString, sig: RabinSig, winnerSig: Sig) {69 // Verify oracle signature.70 assert(71 RabinVerifierWOC.verifySig(msg, sig, this.oraclePubKey),72 'Oracle sig verify failed.'73 )74 75 // Decode data.76 const exchangeRate = PriceBet.parseExchangeRate(msg)77 78 // Validate data.79 assert(80 exchangeRate.timestamp >= this.timestampFrom,81 'Timestamp too early.'82 )83 assert(84 exchangeRate.timestamp <= this.timestampTo,85 'Timestamp too late.'86 )87 assert(exchangeRate.symbol == this.symbol, 'Wrong symbol.')88 89 // Decide winner and check their signature.90 const winner =91 exchangeRate.price >= this.targetPrice92 ? this.alicePubKey93 : this.bobPubKey94 assert(this.checkSig(winnerSig, winner))95}96```97 98Let's walk through each part.99 100First, we verify that the passed signature is correct. For that we use the `RabinVerifierWOC` library from the [`scrypt-ts-lib`](https://www.npmjs.com/package/scrypt-ts-lib) package101 102```ts103import { RabinPubKey, RabinSig, RabinVerifierWoc } from 'scrypt-ts-lib'104```105 106Now, we can call the `verifySig` method of the verification library:107```ts108// Verify oracle signature.109assert(110 RabinVerifierWOC.verifySig(msg, sig, this.oraclePubKey),111 'Oracle sig verify failed.'112)113``` 114The verification method requires the message signed by the oracle, the oracles signature for the message, and the oracle's public key, which we already set via the constructor.115 116Next, we need to parse information from the chunk of data that is the signed message and assert on it. For a granular description of the message format check out the `"Exchange Rate"` section in the [WitnessOnChain API docs](https://witnessonchain.com).117 118We need to implement the static method `parseExchangeRate` as follows:119 120```ts121// Parses signed message from the oracle.122@method()123static parseExchangeRate(msg: ByteString): ExchangeRate {124 // 4 bytes timestamp (LE) + 8 bytes rate (LE) + 1 byte decimal + 16 bytes symbol125 return {126 timestamp: Utils.fromLEUnsigned(slice(msg, 0n, 4n)),127 price: Utils.fromLEUnsigned(slice(msg, 4n, 12n)),128 symbol: slice(msg, 13n, 29n),129 }130}131```132 133We parse out the following data:134- `timestamp` - The time at which this exchange rate is present.135- `price` - The exchange rate encoded as an integer -> (priceFloat * (10^decimal)).136- `symbol` - The symbol of the token pair, e.g. `BSV_USDC`.137 138Finally, we wrap the parsed values in a custom type, named `ExchangeRate` and return it. Here's the definition of the type:139 140```ts141type ExchangeRate = {142 timestamp: bigint143 price: bigint144 symbol: ByteString145}146```147 148Now we can validate the data. First, we check if the timestamp of the exchange rate is within our specified range that we bet on:149 150```ts151assert(152 exchangeRate.timestamp >= this.timestampFrom,153 'Timestamp too early.'154)155assert(156 exchangeRate.timestamp <= this.timestampTo,157 'Timestamp too late.'158)159```160 161Additionally, we check if the exchange rate is actually for the correct token pair:162 163```ts164assert(exchangeRate.symbol == this.symbol, 'Wrong symbol.')165```166 167Lastly, upon having all the necessary information, we can choose the winner and check their signature:168 169```ts170const winner =171 exchangeRate.price >= this.targetPrice172 ? this.alicePubKey173 : this.bobPubKey174assert(this.checkSig(winnerSig, winner))175```176 177As we can see, if the target price is reached, only Alice is able to unlock the funds, and if not, then only Bob is able to do so.178 179 180## Conclusion181 182Congratulations! You have completed the oracle tutorial!183 184The full code along with [tests](https://github.com/sCrypt-Inc/boilerplate/blob/master/tests/local/priceBet.test.ts) can be found in sCrypt's [boilerplate repository](https://github.com/sCrypt-Inc/boilerplate/blob/master/src/contracts/priceBet.ts).185 186 