redstone_rust_sdk/
lib.rs

1use bitmaps::Bitmap;
2use ink_env::hash::{HashOutput, Keccak256};
3use std::convert::TryInto;
4
5const REDSTONE_MARKER_BS: usize = 9;
6const UNSIGNED_METADATA_BYTE_SIZE_BS: usize = 3;
7const DATA_PACKAGES_COUNT_BS: usize = 2;
8const DATA_POINTS_COUNT_BS: usize = 3;
9const SIGNATURE_BS: usize = 65;
10const MAX_SIGNERS_COUNT: usize = 256;
11const DATA_POINT_VALUE_BYTE_SIZE_BS: usize = 4;
12const DATA_FEED_ID_BS: usize = 32;
13const TIMESTAMP_BS: usize = 6;
14const MAX_TIMESTAMP_DELAY_MS: u128 = 3 * 60 * 1000; // 3 minutes in milliseconds
15const REDSTONE_MARKER: [u8; 9] = [0, 0, 2, 237, 87, 1, 30, 0, 0]; // 0x000002ed57011e0000
16
17struct DataPackageExtractionResult {
18    contains_requested_data_feed: bool,
19    value_for_requested_data_feed: u128,
20    signer_index: usize,
21    data_package_byte_size: usize,
22}
23
24pub fn get_oracle_value(
25    data_feed_id: &[u8; 32],
26    unique_signers_threshold: u8,
27    authorised_signers: &[[u8; 33]],
28    current_timestamp_milliseconds: u128,
29    redstone_payload: &[u8],
30) -> u128 {
31    assert_valid_redstone_marker(redstone_payload);
32    let mut negative_offset = extract_unsigned_metadata_offset(redstone_payload);
33    let number_of_data_packages =
34        extract_number_of_data_packages(redstone_payload, negative_offset);
35    negative_offset += DATA_PACKAGES_COUNT_BS;
36    let mut unique_signers_bitmap = Bitmap::<MAX_SIGNERS_COUNT>::new();
37    let mut values: Vec<u128> = vec![];
38
39    for _data_package_index in 0..number_of_data_packages {
40        let DataPackageExtractionResult {
41            contains_requested_data_feed,
42            value_for_requested_data_feed,
43            signer_index,
44            data_package_byte_size,
45        } = extract_data_package(
46            data_feed_id,
47            redstone_payload,
48            negative_offset,
49            authorised_signers,
50            current_timestamp_milliseconds,
51        );
52
53        // Shifting negative offset to the next package
54        negative_offset += data_package_byte_size;
55
56        // Collect value if needed
57        if contains_requested_data_feed && !unique_signers_bitmap.get(signer_index) {
58            unique_signers_bitmap.set(signer_index, true);
59            values.push(value_for_requested_data_feed)
60        }
61    }
62
63    if values.len() < usize::from(unique_signers_threshold) {
64        panic!("Insufficient number of unique signers");
65    }
66
67    aggregate_values(&mut values)
68}
69
70fn assert_valid_redstone_marker(redstone_payload: &[u8]) {
71    let marker_start_index = redstone_payload.len() - REDSTONE_MARKER_BS;
72    let redstone_marker = &redstone_payload[marker_start_index..];
73    if REDSTONE_MARKER != redstone_marker {
74        panic!("Invalid redstone marker");
75    }
76}
77
78fn extract_unsigned_metadata_offset(redstone_payload: &[u8]) -> usize {
79    let end_index = redstone_payload.len() - REDSTONE_MARKER_BS; // not inclusive
80    let start_index = end_index - UNSIGNED_METADATA_BYTE_SIZE_BS;
81    let unsigned_metadata_bs =
82        extract_usize_num_from_redstone_payload(redstone_payload, start_index, end_index);
83
84    unsigned_metadata_bs + UNSIGNED_METADATA_BYTE_SIZE_BS + REDSTONE_MARKER_BS
85}
86
87fn extract_number_of_data_packages(
88    redstone_payload: &[u8],
89    unsigned_metadata_offset: usize,
90) -> usize {
91    let end_index = redstone_payload.len() - unsigned_metadata_offset;
92    let start_index = end_index - DATA_PACKAGES_COUNT_BS;
93    extract_usize_num_from_redstone_payload(redstone_payload, start_index, end_index)
94}
95
96fn extract_data_package(
97    requested_data_feed_id: &[u8; 32],
98    redstone_payload: &[u8],
99    negative_offset_to_package: usize,
100    authorised_signers: &[[u8; 33]],
101    current_timestamp_milliseconds: u128,
102) -> DataPackageExtractionResult {
103    let mut value_for_requested_data_feed: u128 = 0;
104    let mut contains_requested_data_feed = false;
105    let mut signer_index: usize = 0;
106
107    // Extracting signature
108    let mut end_index = redstone_payload.len() - negative_offset_to_package;
109    let mut start_index = end_index - SIGNATURE_BS;
110    let signature = &redstone_payload[start_index..end_index];
111
112    // Extracting number of data points
113    start_index -= DATA_POINTS_COUNT_BS;
114    end_index = start_index + DATA_POINTS_COUNT_BS;
115    let data_points_count =
116        extract_usize_num_from_redstone_payload(redstone_payload, start_index, end_index);
117
118    // Extracting data points value byte size
119    start_index -= DATA_POINT_VALUE_BYTE_SIZE_BS;
120    end_index = start_index + DATA_POINT_VALUE_BYTE_SIZE_BS;
121    let data_points_value_bs =
122        extract_usize_num_from_redstone_payload(redstone_payload, start_index, end_index);
123
124    // Calculating total data package byte size
125    let data_package_byte_size_without_sig = (data_points_value_bs + DATA_FEED_ID_BS)
126        * data_points_count
127        + TIMESTAMP_BS
128        + DATA_POINT_VALUE_BYTE_SIZE_BS
129        + DATA_POINTS_COUNT_BS;
130
131    // Extracting and validating timestamp
132    start_index -= TIMESTAMP_BS;
133    end_index = start_index + TIMESTAMP_BS;
134    let timestamp_milliseconds = bytes_arr_to_number(&redstone_payload[start_index..end_index]);
135    validate_timestamp(timestamp_milliseconds, current_timestamp_milliseconds);
136
137    // Going through data points
138    for _data_point_index in 0..data_points_count {
139        // Extracting value
140        start_index -= data_points_value_bs;
141        end_index = start_index + data_points_value_bs;
142        let data_point_value = bytes_arr_to_number(&redstone_payload[start_index..end_index]);
143
144        // Extracting data feed id
145        start_index -= DATA_FEED_ID_BS;
146        end_index = start_index + DATA_FEED_ID_BS;
147        let data_feed_id = &redstone_payload[start_index..end_index];
148
149        if data_feed_id == requested_data_feed_id {
150            value_for_requested_data_feed = data_point_value;
151            contains_requested_data_feed = true;
152            break;
153        }
154    }
155
156    // Message construction
157    end_index = redstone_payload.len() - (negative_offset_to_package + SIGNATURE_BS);
158    start_index = end_index - data_package_byte_size_without_sig;
159    let signable_message = &redstone_payload[start_index..end_index];
160
161    // Hashing message
162    let mut message_hash = <Keccak256 as HashOutput>::Type::default(); // 256-bit buffer
163    ink_env::hash_bytes::<Keccak256>(signable_message, &mut message_hash);
164
165    // Recovering signer public key
166    let mut recovered_signer = [0; 33];
167    ink_env::ecdsa_recover(
168        &(signature.try_into().unwrap()),
169        &message_hash,
170        &mut recovered_signer,
171    )
172    .unwrap();
173
174    // Signer verification
175    let mut signer_is_authorised = false;
176    for (authorised_signer_index, authorised_signer) in authorised_signers.iter().enumerate() {
177        if authorised_signer == &recovered_signer {
178            signer_index = authorised_signer_index;
179            signer_is_authorised = true;
180        }
181    }
182    if !signer_is_authorised {
183        panic!("Signer is not authorised");
184    }
185
186    // Prepare result
187    DataPackageExtractionResult {
188        contains_requested_data_feed,
189        value_for_requested_data_feed,
190        data_package_byte_size: data_package_byte_size_without_sig + SIGNATURE_BS,
191        signer_index,
192    }
193}
194
195fn aggregate_values(values: &mut Vec<u128>) -> u128 {
196    if values.len() == 0 {
197        panic!("Can not take median of an empty array");
198    }
199    values.sort();
200    let mid = values.len() / 2;
201    if values.len() % 2 == 0 {
202        return (values[mid - 1] + values[mid]) / 2;
203    } else {
204        return values[mid];
205    }
206}
207
208fn validate_timestamp(received_timestamp_milliseconds: u128, current_timestamp_milliseconds: u128) {
209    if received_timestamp_milliseconds + MAX_TIMESTAMP_DELAY_MS < current_timestamp_milliseconds {
210        panic!("Timestamp is too old");
211    }
212}
213
214fn extract_usize_num_from_redstone_payload(
215    redstone_payload: &[u8],
216    start: usize,
217    end: usize,
218) -> usize {
219    let number_bytes = &redstone_payload[start..end];
220    usize::try_from(bytes_arr_to_number(number_bytes)).unwrap()
221}
222
223fn bytes_arr_to_number(number_bytes: &[u8]) -> u128 {
224    let mut result_number = 0;
225    let mut multiplier = 1;
226
227    for i in (0..number_bytes.len()).rev() {
228        // To prevent overflow error
229        if i == 16 {
230            break;
231        }
232        result_number += u128::from(number_bytes[i]) * multiplier;
233        multiplier *= 256;
234    }
235
236    result_number
237}