Icon HelpCircleForumIcon Link

⌘K

Icon HelpCircleForumIcon Link
Src 17 Naming Verification

Icon LinkSRC-17: Naming Verification Standard

The following standard defines a naming verification standard for onchain identities using offchain data.

Icon LinkMotivation

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.

Icon LinkPrior Art

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) Icon Link. 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 Icon Link and Fuel Name Service(FNS) Icon Link. This standard was developed in close collaboration with these two platforms to ensure compatibility and ease of upgrade.

Icon LinkSpecification

Icon LinkType Aliases

Icon LinkAltBn128Proof 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];

Icon LinkSparseMerkleProof 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;

Icon LinkEnums

Icon LinkSRC17VerificationError 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:

Icon LinkVerificationFailed

The VerificationFailed variant SHALL be used when verification of a name fails for ANY reason.

pub enum SRC17VerificationError {
    VerificationFailed: (),
}

Icon LinkSRC17Proof 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:

Icon LinkAltBn128Proof

The AltBn128Proof variant SHALL be used for AltBn128 proofs.

Icon LinkSparseMerkleProof

The SparseMerkleProof variant SHALL be used for Sparse Merkle Tree proofs.

pub enum SRC17Proof {
    AltBn128Proof: AltBn128Proof,
    SparseMerkleProof: SparseMerkleProof,
}

Icon LinkRequired Public Functions

The following functions MUST be implemented to follow the SRC-17 standard:

Icon Linkfn verify(proof: SRC17Proof, name: String, resolver: Identity, asset: AssetId, metadata: Option<Bytes>) -> Result<(), SRC17VerificationError>

  • This function MUST return Ok(()) if the proof verification was successful. Otherwise, it MUST return Err(SRC17VerificationError::VerificationFailed).
  • The proof argument MUST be an SRC17Proof which proves the name, asset, resolver, and metadata are valid and included in the data.
  • The name argument MUST the corresponding String for the onchain human-readable identity.
  • The resolver argument MUST be the identity which the name is pointing to.
  • The asset argument MUST be the asset that represents ownership of a name.
  • The metadata argument MUST contain Some bytes associated with the name or MUST be None.

Icon LinkLogging

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.

Icon LinkSRC17NameEvent

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>
}

Icon LinkRationale

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.

Icon LinkBackwards Compatibility

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.

Icon LinkSecurity Considerations

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.

Icon LinkExample ABI

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>;
}

Icon LinkExample Implementation

Icon LinkSparse Merkle Verification Example

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);
    }
}