The following standard defines a naming verification standard for onchain identities using offchain data.
A standard interface for names on Fuel allows external applications to verify and determine the resolver of a name, whether that be decentralized exchanges frontends, wallets, explorers, or other infrastructure providers.
A number of existing name service platforms already existed prior to the development of this standard. Notably the most well-known on the Ethereum Blockchain is the Ethereum Name Service(ENS) . This domain service has a major influence on other name services across multiple platforms. ENS pioneered name service platforms and this standard takes some inspiration from their resolver design.
On Fuel, we have 2 existing name service platforms. These are Bako ID and Fuel Name Service(FNS) . This standard was developed in close collaboration with these two platforms to ensure compatibility and ease of upgrade.
AltBn128Proof
Type Alias The following describes a type alias for AltBn128 proofs. The AltBn128Proof
SHALL be defined as the fixed length array [u8; 288]
.
pub type AltBn128Proof = [u8; 288];
SparseMerkleProof
Type Alias The following describes a type alias for Sparse Merkle Tree proofs. The SparseMerkleProof
SHALL be defined as the Proof
type from the Sway-Libs Sparse Merkle Tree Library.
pub type SparseMerkleProof = Proof;
SRC17VerificationError
Enum The following describes an enum that is used when verification of a name fails. There SHALL be the following variants in the SRC17VerificationError
type:
VerificationFailed
The VerificationFailed
variant SHALL be used when verification of a name fails for ANY reason.
pub enum SRC17VerificationError {
VerificationFailed: (),
}
SRC17Proof
Enum The following describes an enum that wraps various proof types into a single input type. There SHALL be the following variants in the SRC17Proof
type:
AltBn128Proof
The AltBn128Proof
variant SHALL be used for AltBn128 proofs.
SparseMerkleProof
The SparseMerkleProof
variant SHALL be used for Sparse Merkle Tree proofs.
pub enum SRC17Proof {
AltBn128Proof: AltBn128Proof,
SparseMerkleProof: SparseMerkleProof,
}
The following functions MUST be implemented to follow the SRC-17 standard:
fn verify(proof: SRC17Proof, name: String, resolver: Identity, asset: AssetId, metadata: Option<Bytes>) -> Result<(), SRC17VerificationError>
Ok(())
if the proof verification was successful. Otherwise, it MUST return Err(SRC17VerificationError::VerificationFailed)
. proof
argument MUST be an SRC17Proof
which proves the name
, asset
, resolver
, and metadata
are valid and included in the data. name
argument MUST the corresponding String
for the onchain human-readable identity. resolver
argument MUST be the identity which the name is pointing to. asset
argument MUST be the asset that represents ownership of a name. metadata
argument MUST contain Some
bytes associated with the name or MUST be None
. The following logs MUST be implemented and emitted to follow the SRC-17 standard. Logs MUST be emitted if there are changes that update ANY proof.
The SRC17NameEvent
MUST be emitted when ANY data changes occur.
There SHALL be the following fields in the SRC17UpdateEvent
struct:
name
: The name
field SHALL be used for the corresponding String
which represents the name. resolver
: The resolver
field SHALL be used for the corresponding Identity
to which the name points. asset
: The asset
field SHALL be used for the corresponding AssetId
that represents ownership of a name. metadata
: The metadata
field MUST contain Some
bytes associated with the name or MUST be None
. Example:
pub struct SRC17NameEvent {
pub name: String,
pub resolver: Identity,
pub asset: AssetId,
pub metadata: Option<Bytes>
}
The development and implementation of this standard should enable the verification of names for infrastructure providers such as explorers, wallets, and more. Standardizing the verification method and leaving the implementation up to interpretation shall leave room for experimentation and differentiating designs between projects.
Additionally, the use of proofs should reduce the onchain footprint and minimize state. This standard notably has no expiry, a feature of most name service platforms. Should a project wish to implement an expiry, it should be included as part of the metadata.
This standard is compatible with the existing name standards in the Fuel ecosystem, namely Bako ID and Fuel Name Service(FNS). There are no other standards that require compatibility.
This standard does not introduce any security concerns, as it does not call external contracts, nor does it define any mutations of the contract state.
pub type AltBn128Proof = [u8; 288];
pub type SparseMerkleProof = Proof;
pub enum SRC17Proof {
AltBn128Proof: AltBn128Proof,
SparseMerkleProof: SparseMerkleProof,
}
pub enum SRC17VerificationError {
VerificationFailed: (),
}
abi SRC17 {
#[storage(read)]
fn verify(proof: SRC17Proof, name: String, resolver: Identity, asset: AssetId, metadata: Option<Bytes>) -> Result<(), SRC17VerificationError>;
}
An example of the SRC-17 implementation where a Sparse Merkle Tree is used to verify the validity of names. In the example, the name
is the key in the Sparse Merkle Tree and the asset
, resolver
, and metadata
are the data which make up the leaf. The example supports both inclusion and exclusion proofs. If an AltBn128 proof is provided instead, the verification fails.
contract;
use std::{bytes::Bytes, hash::{Hash, sha256}, string::String};
use standards::src17::*;
use sway_libs::merkle::{common::MerkleRoot, sparse::*};
storage {
merkle_root: MerkleRoot = MerkleRoot::zero(),
}
impl SRC17 for Contract {
#[storage(read)]
fn verify(
proof: SRC17Proof,
name: String,
resolver: Identity,
asset: AssetId,
metadata: Option<Bytes>,
) -> Result<(), SRC17VerificationError> {
match proof {
SRC17Proof::AltBn128Proof(_) => Err(SRC17VerificationError::VerificationFailed),
SRC17Proof::SparseMerkleProof(proof) => {
let key: MerkleTreeKey = sha256(name);
match proof {
Proof::Inclusion => {
// Combine the resolver, asset, and metadata into to a single Byte array.
let mut leaf_bytes = Bytes::new();
leaf_bytes.append(resolver.bits().into());
leaf_bytes.append(asset.bits().into());
match metadata {
Some(metadata_bytes) => {
leaf_bytes.append(metadata_bytes);
},
None => (),
}
if proof.verify(storage.merkle_root.read(), key, Some(leaf_bytes))
{
Ok(())
} else {
Err(SRC17VerificationError::VerificationFailed)
}
},
Proof::Exclusion => {
if proof.verify(storage.merkle_root.read(), key, None) {
Ok(())
} else {
Err(SRC17VerificationError::VerificationFailed)
}
}
}
}
}
}
}
abi UpdateData {
fn data_updated(
name: String,
resolver: Identity,
asset: AssetId,
metadata: Option<Bytes>,
);
}
impl UpdateData for Contract {
fn data_updated(
name: String,
resolver: Identity,
asset: AssetId,
metadata: Option<Bytes>,
) {
// NOTE: There are no checks for whether someone has the permission to do this.
// It is suggested to add some administrative controls such as the Sway-Libs Ownership Library.
let event = SRC17NameEvent::new(name, resolver, asset, metadata);
event.log();
}
}
abi SetupExample {
#[storage(write)]
fn initialize(root: MerkleRoot);
}
impl SetupExample for Contract {
#[storage(write)]
fn initialize(root: MerkleRoot) {
// NOTE: There are no checks for whether someone has the permission to do this.
// It is suggested to add some administrative controls such as the Sway-Libs Ownership Library.
storage.merkle_root.write(root);
}
}