-
Notifications
You must be signed in to change notification settings - Fork 223
Add DNS oracle protocol #917
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| # Oracle DNS Protocol | ||
|
|
||
| The Oracle plugin resolves RFC 4501 `dns:` URIs through a DNS-over-HTTPS (DoH) gateway. This lets oracle nodes read authoritative DNS data (TXT for DKIM/SPF/DIDs, CERT/TLSA, etc.) without sending plaintext DNS queries. | ||
|
|
||
| > **When should I use it?** | ||
| > Whenever you need DNS data on-chain and want the request to stay encrypted end-to-end. | ||
| ## Enable and configure | ||
|
|
||
| 1. Install or build the `OracleService` plugin and copy `OracleService.json` next to the plugin binary. | ||
| 2. Add the `Dns` section (defaults shown): | ||
|
|
||
| ```jsonc | ||
| { | ||
| "PluginConfiguration": { | ||
| // ... | ||
| "Dns": { | ||
| "EndPoint": "https://cloudflare-dns.com/dns-query", | ||
| "Timeout": 5000 | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| - `EndPoint` must point to a DoH resolver that understands the [application/dns-json](https://developers.cloudflare.com/api/operations/dns-over-https) format. | ||
| - `Timeout` is the maximum milliseconds the oracle will wait for a DoH response before returning `OracleResponseCode.Timeout`. | ||
|
|
||
| > You can run your own DoH gateway and point the oracle to it if you need custom trust anchors or strict egress controls. | ||
| ## RFC 4501 URI format | ||
|
|
||
| ``` | ||
| dns:[//authority/]domain[?CLASS=class;TYPE=type][;FORMAT=x509] | ||
| ``` | ||
|
|
||
| - `domain` is the DNS owner name (relative or absolute). Percent-encoding and escaped dots (`%5c.`) follow RFC 4501 rules. | ||
| - `domain` must not include additional path segments; only the owner name belongs here. | ||
| - `authority` is the optional DNS server hint from RFC 4501. The oracle still uses the DoH resolver configured in `OracleService.json`. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use the user-specified authority? |
||
| - `CLASS` is optional and case-insensitive. Only `IN` (`1`) is supported; other classes are rejected. | ||
| - `TYPE` is optional and case-insensitive. Use mnemonics (`TXT`, `TLSA`, `CERT`, `A`, `AAAA`, …) or numeric values. Defaults to `A` per RFC 4501. | ||
| - `FORMAT` is an oracle extension; use `format=x509` (or `cert`) to parse TXT/CERT payloads into the `Certificate` field. | ||
| - `name` is an oracle extension; if present, it overrides `domain` entirely (useful for percent-encoding complex owner names). | ||
|
|
||
| Query parameters can be separated by `;` (RFC style) or `&`. | ||
|
|
||
| Examples: | ||
|
|
||
| - `dns:1alhai._domainkey.icloud.com?TYPE=TXT` — DKIM TXT record. | ||
| - `dns:simon.example.org?TYPE=CERT;FORMAT=x509` — extract the X.509 payload into `Certificate`. | ||
| - `dns://192.168.1.1/ftp.example.org?TYPE=A` — RFC-compliant authority form (authority is ignored; the configured DoH endpoint is used). | ||
| - `dns:ignored?name=weird%5c.label.example&type=TXT` — uses the `name` override (decoded to `weird\.label.example`). | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why can a DNS name contain backslashes ( |
||
|
|
||
| ## Response schema | ||
|
|
||
| Successful queries return UTF-8 JSON. Attributes correspond to the `ResultEnvelope` produced by the oracle: | ||
|
|
||
| ```jsonc | ||
| { | ||
| "Name": "1alhai._domainkey.icloud.com", | ||
| "Type": "TXT", | ||
| "Answers": [ | ||
| { | ||
| "Name": "1alhai._domainkey.icloud.com", | ||
| "Type": "TXT", | ||
| "Ttl": 299, | ||
| "Data": "\"k=rsa; p=...IDAQAB\"" | ||
| } | ||
| ], | ||
| "Certificate": { | ||
| "Subject": "CN=example.com", | ||
| "Issuer": "CN=Example Root", | ||
| "Thumbprint": "ABCD1234...", | ||
| "NotBefore": "2024-01-16T00:00:00Z", | ||
| "NotAfter": "2025-01-16T00:00:00Z", | ||
| "Der": "MIIC...", | ||
| "PublicKey": { | ||
| "Algorithm": "RSA", | ||
| "Encoded": "MIIBIjANBg...", | ||
| "Modulus": "B968DE...", | ||
| "Exponent": "010001" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| - `Answers` mirrors the DoH response but normalizes record types and names. | ||
| - `Certificate` is present only when `TYPE=CERT` or `FORMAT=x509`. `Der` is the base64-encoded certificate, while `PublicKey` provides both the encoded SubjectPublicKeyInfo (`Encoded`) and algorithm-specific fields (`Modulus`/`Exponent` for RSA, `Curve`/`X`/`Y` for EC). | ||
| - For RSA keys the modulus/exponent strings are big-endian hex. For EC keys the X/Y coordinates are hex-encoded affine coordinates on the reported `Curve`. | ||
| - If the DoH server responds with NXDOMAIN, the oracle returns `OracleResponseCode.NotFound`. | ||
| - Responses exceeding `OracleResponse.MaxResultSize` yield `OracleResponseCode.ResponseTooLarge`. | ||
|
|
||
| ## Contract usage example | ||
|
|
||
| ```csharp | ||
| public static void RequestAppleDkim() | ||
| { | ||
| const string url = "dns:1alhai._domainkey.icloud.com?TYPE=TXT"; | ||
| Oracle.Request(url, "", nameof(OnOracleCallback), Runtime.CallingScriptHash, 5_00000000); | ||
| } | ||
|
|
||
| public static void OnOracleCallback(string url, byte[] userData, int code, byte[] result) | ||
| { | ||
| if (code != (int)OracleResponseCode.Success) throw new Exception("Oracle query failed"); | ||
|
|
||
| var envelope = (Neo.SmartContract.Framework.Services.Neo.Json.JsonObject)StdLib.JsonDeserialize(result); | ||
| var answers = (Neo.SmartContract.Framework.Services.Neo.Json.JsonArray)envelope["Answers"]; | ||
| var txt = (Neo.SmartContract.Framework.Services.Neo.Json.JsonObject)answers[0]; | ||
| Storage.Put(Storage.CurrentContext, "dkim", txt["Data"].AsString()); | ||
| } | ||
| ``` | ||
|
|
||
| Tips: | ||
|
|
||
| 1. Always set `TYPE` when you need anything other than an A record. | ||
| 2. Budget enough `gasForResponse` to cover JSON payload size (TXT records are often kilobytes). | ||
| 3. Validate TTL or fingerprint data before trusting it. | ||
| 4. Combine oracle DNS data with existing filters (e.g., `Helper.JsonPath`/`OracleService.Filter`) if you only need a slice of the result. | ||
|
|
||
| ## Manual testing | ||
|
|
||
| Use the same resolver the oracle will contact to inspect responses: | ||
|
|
||
| ```bash | ||
| curl -s \ | ||
| -H 'accept: application/dns-json' \ | ||
| 'https://cloudflare-dns.com/dns-query?name=1alhai._domainkey.icloud.com&type=TXT' | ||
| ``` | ||
|
|
||
| Compare the JSON payload with the data returned by your contract callback to ensure parity. | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -36,6 +36,19 @@ public NeoFSSettings(IConfigurationSection section) | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| class DnsSettings | ||||||
| { | ||||||
| public Uri EndPoint { get; } | ||||||
| public TimeSpan Timeout { get; } | ||||||
|
|
||||||
| public DnsSettings(IConfigurationSection section) | ||||||
| { | ||||||
| string endpoint = section.GetValue("EndPoint", "https://cloudflare-dns.com/dns-query"); | ||||||
| EndPoint = new Uri(endpoint, UriKind.Absolute); | ||||||
| Timeout = TimeSpan.FromMilliseconds(section.GetValue("Timeout", 5000)); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| } | ||||||
| } | ||||||
|
|
||||||
| class OracleSettings : IPluginSettings | ||||||
| { | ||||||
| public uint Network { get; } | ||||||
|
|
@@ -46,6 +59,7 @@ class OracleSettings : IPluginSettings | |||||
| public string[] AllowedContentTypes { get; } | ||||||
| public HttpsSettings Https { get; } | ||||||
| public NeoFSSettings NeoFS { get; } | ||||||
| public DnsSettings Dns { get; } | ||||||
| public bool AutoStart { get; } | ||||||
|
|
||||||
| public static OracleSettings Default { get; private set; } | ||||||
|
|
@@ -65,6 +79,7 @@ private OracleSettings(IConfigurationSection section) | |||||
| AllowedContentTypes = AllowedContentTypes.Concat("application/json").ToArray(); | ||||||
| Https = new HttpsSettings(section.GetSection("Https")); | ||||||
| NeoFS = new NeoFSSettings(section.GetSection("NeoFS")); | ||||||
| Dns = new DnsSettings(section.GetSection("Dns")); | ||||||
| AutoStart = section.GetValue("AutoStart", false); | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What Unit ?
Millseconds?