Sigma Auth
Reference

BAP Profiles

Bitcoin Attestation Protocol (BAP) provides decentralized identity management on the Bitcoin blockchain. Sigma Auth automatically fetches and includes BAP profile data when available.

What is BAP?

BAP (Bitcoin Attestation Protocol) is a decentralized identity system that stores profile information on the Bitcoin blockchain. It allows users to create verified, immutable profiles linked to their Bitcoin identity.

BAP profiles follow an extended version of schema.org standards:

While maintaining schema.org compatibility, BAP extends these schemas with Bitcoin-specific fields:

  • rootAddress - Root Bitcoin (BSV) address for the identity
  • currentAddress - Current signing address (may be rotated)
  • idKey - BAP identity key (base58 encoded)
  • paymail - Human-readable payment address (in identity data)
  • Additional blockchain-related attestations

This hybrid approach ensures BAP profiles are interoperable with standard web tools while supporting Bitcoin's unique requirements.

BAP Structure

The actual structure returned by api.sigmaidentity.com:

interface BAPProfile {
  idKey: string;              // BAP identity key (e.g. "A4PYmuKGG61WCjjBaRpuSEbqytG")
  rootAddress: string;        // Root Bitcoin address
  currentAddress?: string;    // Current signing address (may be rotated)
  addresses?: Array<{         // Historical address chain
    address: string;
    txId: string;
    block: number;
  }>;
  identity?: {                // Schema.org Person/Organization data
    "@context"?: string;      // "https://schema.org"
    "@type"?: string;         // "Person" or "Organization"
    alternateName?: string;   // Display name/username
    givenName?: string;       // First name
    familyName?: string;      // Last name
    description?: string;     // Profile bio
    image?: string;           // Profile image URL
    banner?: string;          // Banner image URL
    url?: string;             // Website URL
    email?: string;           // Email address
    paymail?: string;         // Paymail address
    [key: string]: unknown;   // Additional schema.org fields
  };
  firstSeen?: number;         // Block height when first seen
  txHash?: string;            // Transaction hash
}

Getting BAP Profiles

Example: Demo User Lookup

Here's how Sigma Auth fetches BAP profiles internally. Using the demo identity Go8vCHAa4S6AhXKTABGpANiz35J:

# Direct API call to fetch BAP identity
curl -X POST https://api.sigmaidentity.com/api/v1/identity/get \
  -H "Content-Type: application/json" \
  -d '{"idKey": "Go8vCHAa4S6AhXKTABGpANiz35J"}'

Response:

{
  "status": "OK",
  "result": {
    "idKey": "Go8vCHAa4S6AhXKTABGpANiz35J",
    "rootAddress": "1GXdTRDtQGKZ6yp2oUkDf8SuVhBf8Ww7ki",
    "currentAddress": "1PHiKmPMqUSi5AWDYHMT3CvQ9rFAFnax8j",
    "addresses": [
      {
        "address": "1PHiKmPMqUSi5AWDYHMT3CvQ9rFAFnax8j",
        "txId": "dcc70a6b3a2ef400177f17c6b2d78b43015b13b608a4701c0245d1e4736686bb",
        "block": 864381
      }
    ],
    "identity": {
      "@type": "Person",
      "@context": "https://schema.org",
      "alternateName": "DemoUser",
      "givenName": "Demo",
      "familyName": "User",
      "description": "Example BAP profile",
      "image": "https://ordfs.network/abc123_62",
      "banner": "https://ordfs.network/def456",
      "email": "demo@example.com",
      "url": "https://demo.example.com",
      "paymail": "demo@example.com"
    },
    "firstSeen": 864381,
    "txHash": "dcc70a6b3a2ef400177f17c6b2d78b43015b13b608a4701c0245d1e4736686bb"
  }
}

Sigma Auth includes this complete structure in the bap field of the /userinfo response.

From UserInfo Endpoint

BAP profiles are automatically included in the /userinfo endpoint response when available:

const response = await fetch('https://auth.sigmaidentity.com/userinfo', {
  headers: {
    'Authorization': `Bearer ${accessToken}`
  }
});

const userInfo = await response.json();
console.log(userInfo);

Response with BAP Profile

When a BAP profile exists, the userinfo endpoint returns standard OIDC claims plus custom pubkey and bap fields:

{
  "sub": "user_abc123def456",
  "name": "JohnDoe",
  "given_name": "John",
  "family_name": "Doe",
  "picture": "https://ordfs.network/abc123_62",
  "pubkey": "03a1b2c3d4e5f6789...",
  "bap": {
    "idKey": "A4PYmuKGG61WCjjBaRpuSEbqytG",
    "rootAddress": "1GXdTRDtQGKZ6yp2oUkDf8SuVhBf8Ww7ki",
    "currentAddress": "1PHiKmPMqUSi5AWDYHMT3CvQ9rFAFnax8j",
    "addresses": [
      {
        "address": "1PHiKmPMqUSi5AWDYHMT3CvQ9rFAFnax8j",
        "txId": "dcc70a6b3a...",
        "block": 864381
      }
    ],
    "identity": {
      "@type": "Person",
      "@context": "https://schema.org",
      "alternateName": "JohnDoe",
      "givenName": "John",
      "familyName": "Doe",
      "description": "Bitcoin developer",
      "image": "https://ordfs.network/abc123_62",
      "paymail": "john@example.com",
      "url": "https://johndoe.com"
    },
    "firstSeen": 864381,
    "txHash": "dcc70a6b3a..."
  }
}

Response without BAP Profile

When no BAP profile exists (user hasn't published on-chain):

{
  "sub": "user_abc123def456",
  "name": "1BitcoinAddress...",
  "pubkey": "03a1b2c3d4e5f6789...",
  "bap": null
}

Profile Availability

When BAP Profiles Are Available

  • ✅ User has created a BAP profile on the blockchain
  • ✅ Profile is properly formatted and parseable
  • ✅ Profile attestations are valid
  • ✅ Sigma API can successfully fetch the profile

When BAP Profiles Are Not Available

  • ❌ User has never created a BAP profile
  • ❌ Profile data is corrupted or unparseable
  • ❌ Network issues fetching from Sigma API
  • ❌ Profile attestations are invalid

Handling Missing Profiles

Frontend Implementation

Always check for profile data availability:

function UserProfile({ userInfo }) {
  const hasFullProfile = userInfo.bap?.identity;

  if (hasFullProfile) {
    return (
      <div className="profile">
        <img src={userInfo.picture} alt="Profile" />
        <h2>{userInfo.name}</h2>
        <p>{userInfo.bap.identity.description}</p>
        {userInfo.bap.identity.paymail && (
          <p>Paymail: {userInfo.bap.identity.paymail}</p>
        )}
        <p>BAP ID: {userInfo.bap.idKey}</p>
      </div>
    );
  }

  // Fallback for users without BAP profiles
  return (
    <div className="profile">
      <div className="avatar-placeholder">
        {userInfo.pubkey.slice(0, 2)}
      </div>
      <h2>Bitcoin User</h2>
      <p>Public Key: {userInfo.pubkey}</p>
      <button onClick={() => window.open('https://social.sigmaidentity.com/profile')}>
        Create BAP Profile
      </button>
    </div>
  );
}

Backend Validation

Always validate profile data server-side:

function validateBAPProfile(bap: any): boolean {
  // At minimum, idKey should always be present for valid BAP profiles
  if (!bap?.idKey) {
    return false;
  }

  // Validate rootAddress is present
  if (!bap.rootAddress || typeof bap.rootAddress !== 'string') {
    return false;
  }

  // Validate identity data if present
  if (bap.identity?.image && !isValidURL(bap.identity.image)) {
    return false;
  }

  return true;
}

function processUserInfo(userInfo: any) {
  if (!validateBAPProfile(userInfo.bap)) {
    // Handle invalid or missing BAP profile
    return {
      ...userInfo,
      bap: null // Explicitly set to null if invalid
    };
  }

  return userInfo;
}

Profile Creation Flow

Directing Users to Create Profiles

When users don't have BAP profiles, you can direct them to create one:

function CreateProfilePrompt({ publicKey }) {
  const createProfileURL = `https://social.sigmaidentity.com/profile/create?key=${publicKey}`;
  
  return (
    <div className="profile-prompt">
      <h3>Complete Your Bitcoin Identity</h3>
      <p>Create a BAP profile to add your name, image, and other details to your Bitcoin identity.</p>
      <button onClick={() => window.open(createProfileURL)}>
        Create BAP Profile
      </button>
    </div>
  );
}

Profile Update Detection

Monitor for profile updates in your application:

async function refreshUserProfile(accessToken: string) {
  try {
    const response = await fetch('/userinfo', {
      headers: { 'Authorization': `Bearer ${accessToken}` }
    });
    
    const updatedUserInfo = await response.json();
    
    // Check if profile has been updated
    if (updatedUserInfo.profile?.name && !currentUser.profile?.name) {
      // User has created a new BAP profile
      showNotification('Profile updated successfully!');
      updateUserState(updatedUserInfo);
    }
  } catch (error) {
    console.error('Failed to refresh profile:', error);
  }
}

Common Profile Fields

Top-Level BAP Fields

FieldTypeDescription
idKeystringBAP identity key (always present)
rootAddressstringRoot Bitcoin address
currentAddressstringCurrent signing address
addressesarrayHistorical address chain with txId and block
identityobjectSchema.org Person/Organization data
firstSeennumberBlock height when first seen
txHashstringTransaction hash

Identity Object Fields (schema.org Person)

Fields inside bap.identity:

FieldTypeSchema.org PropertyDescription
@typestring-Schema.org type ("Person" or "Organization")
@contextstring-Schema.org context URL
alternateNamestringPerson.alternateNameDisplay name/username
givenNamestringPerson.givenNameFirst name
familyNamestringPerson.familyNameLast name
descriptionstringPerson.descriptionProfile bio/description
imagestringPerson.imageProfile image URL
bannerstring-Banner image URL
emailstringPerson.emailEmail address
urlstringPerson.urlWebsite URL
paymailstring-Paymail address (Bitcoin-specific)

Extended Fields

BAP identity objects can include additional schema.org compatible fields:

{
  "idKey": "A4PYmuKGG61WCjjBaRpuSEbqytG",
  "rootAddress": "1GXdTRDtQGKZ6yp2oUkDf8SuVhBf8Ww7ki",
  "currentAddress": "1PHiKmPMqUSi5AWDYHMT3CvQ9rFAFnax8j",
  "addresses": [...],
  "identity": {
    "@type": "Person",
    "@context": "https://schema.org",
    "alternateName": "JohnDoe",
    "givenName": "John",
    "familyName": "Doe",
    "description": "Senior Developer",
    "image": "https://ordfs.network/abc123_62",
    "url": "https://johndoe.com",
    "jobTitle": "Senior Developer",
    "worksFor": {
      "@type": "Organization",
      "name": "Bitcoin Corp",
      "url": "https://bitcoincorp.com"
    },
    "address": {
      "@type": "PostalAddress",
      "addressLocality": "San Francisco",
      "addressRegion": "CA",
      "addressCountry": "US"
    },
    "sameAs": [
      "https://twitter.com/johndoe",
      "https://github.com/johndoe",
      "https://linkedin.com/in/johndoe"
    ],
    "knowsAbout": ["Bitcoin", "JavaScript", "Go"]
  },
  "firstSeen": 864381,
  "txHash": "dcc70a6b3a..."
}

Common Extended Fields

FieldSchema.org PropertyDescription
jobTitlePerson.jobTitleCurrent job title
worksForPerson.worksForOrganization/employer
addressPerson.addressLocation/address
sameAsPerson.sameAsLinks to social profiles
knowsAboutPerson.knowsAboutSkills/expertise
alumniOfPerson.alumniOfEducational institutions
memberOfPerson.memberOfOrganizations/groups

Organization Profiles

BAP also supports Organization profiles following schema.org/Organization:

{
  "idKey": "B5QZnvLHH72XDkkCbTqvTFcszuH",
  "rootAddress": "1OrgRootAddress123",
  "currentAddress": "1OrgCurrentAddress456",
  "addresses": [...],
  "identity": {
    "@type": "Organization",
    "@context": "https://schema.org",
    "alternateName": "BitcoinCorp",
    "legalName": "Bitcoin Corporation Inc.",
    "description": "Leading Bitcoin infrastructure provider",
    "logo": "https://ordfs.network/logo123_62",
    "url": "https://bitcoincorp.com",
    "address": {
      "@type": "PostalAddress",
      "streetAddress": "123 Bitcoin St",
      "addressLocality": "San Francisco",
      "addressRegion": "CA",
      "postalCode": "94105",
      "addressCountry": "US"
    },
    "sameAs": [
      "https://twitter.com/bitcoincorp",
      "https://linkedin.com/company/bitcoincorp"
    ],
    "foundingDate": "2019-01-01",
    "founders": [{
      "@type": "Person",
      "name": "Satoshi Nakamoto"
    }]
  },
  "firstSeen": 850000,
  "txHash": "abc123def456..."
}

Schema.org Benefits

Using schema.org standards for BAP profiles provides:

  1. Interoperability - Works with existing tools and services
  2. SEO Benefits - Search engines understand the data structure
  3. Rich Snippets - Enhanced display in search results
  4. Standardization - Consistent data format across applications
  5. Extensibility - Add any schema.org property as needed

Error Handling

Profile Fetch Errors

Handle cases where BAP profile fetching fails:

async function getUserInfo(accessToken: string) {
  try {
    const response = await fetch('/userinfo', {
      headers: { 'Authorization': `Bearer ${accessToken}` }
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const userInfo = await response.json();

    // Validate BAP profile data
    if (userInfo.bap && !userInfo.bap.idKey) {
      console.warn('Invalid BAP profile data - missing idKey');
      userInfo.bap = null;
    }

    return userInfo;
  } catch (error) {
    console.error('Failed to fetch user info:', error);
    throw error;
  }
}

Network Resilience

Implement retry logic for profile fetching:

async function fetchProfileWithRetry(accessToken: string, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await getUserInfo(accessToken);
    } catch (error) {
      if (attempt === maxRetries) {
        // Final attempt failed, return minimal profile
        return {
          sub: 'unknown',
          name: 'Bitcoin User (Offline)',
          pubkey: 'unknown',
          bap: null
        };
      }

      // Wait before retry
      await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
    }
  }
}

Best Practices

1. Always Provide Fallbacks

Never assume BAP profile data will be available:

const displayName = userInfo.name || 'Bitcoin User';
const profileImage = userInfo.picture || '/default-avatar.png';
const bapId = userInfo.bap?.idKey || null;

2. Progressive Enhancement

Build your UI to work without BAP profiles, then enhance with profile data:

function UserCard({ userInfo }) {
  return (
    <div className="user-card">
      {/* Always available */}
      <div className="public-key">{userInfo.pubkey}</div>
      <h3>{userInfo.name}</h3>

      {/* Enhanced with BAP profile */}
      {userInfo.picture && (
        <img src={userInfo.picture} alt="Profile" />
      )}

      {userInfo.bap && (
        <div className="bap-info">
          <p>BAP ID: {userInfo.bap.idKey}</p>
          {userInfo.bap.identity?.description && (
            <p>{userInfo.bap.identity.description}</p>
          )}
        </div>
      )}
    </div>
  );
}

3. Cache Profile Data

Cache profile data to reduce API calls:

const profileCache = new Map();

async function getCachedProfile(publicKey: string) {
  if (profileCache.has(publicKey)) {
    return profileCache.get(publicKey);
  }
  
  const profile = await fetchUserProfile(publicKey);
  profileCache.set(publicKey, profile);
  
  // Cache for 5 minutes
  setTimeout(() => {
    profileCache.delete(publicKey);
  }, 5 * 60 * 1000);
  
  return profile;
}

4. Validate Profile URLs

Always validate URLs in profile data:

function isValidURL(url: string): boolean {
  try {
    const parsed = new URL(url);
    return parsed.protocol === 'https:' || parsed.protocol === 'http:';
  } catch {
    return false;
  }
}

function sanitizeProfileImage(imageUrl: string): string {
  if (!imageUrl || !isValidURL(imageUrl)) {
    return '/default-avatar.png';
  }
  return imageUrl;
}

Resources