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:

  • bitcoinAddress - Primary Bitcoin (BSV) address
  • paymail - Human-readable payment address
  • identityKey - BAP identity public key
  • Additional blockchain-related attestations

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

BAP Structure

interface BAPProfile {
  identityKey: string;     // BAP identity key (different from Bitcoin address)
  
  // Schema.org Person fields
  name?: string;           // Display name (Person.name)
  description?: string;    // Profile bio (Person.description)
  image?: string;          // Profile image URL (Person.image)
  email?: string;          // Email address (Person.email)
  url?: string;            // Website URL (Person.url)
  
  // Additional common fields
  paymail?: string;        // Paymail address
  jobTitle?: string;       // Job title (Person.jobTitle)
  worksFor?: string;       // Organization (Person.worksFor)
  location?: string;       // Location (Person.address)
  
  // Extended attestations following schema.org
  [key: string]: any;      // Additional schema.org compatible fields
}

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",
    "currentAddress": "1Demo...",
    "rootAddress": "1Root...",
    "addresses": ["1Demo...", "1Other..."],
    "identity": {
      "name": "Demo User",
      "alternateName": "Demo",
      "description": "Example BAP profile"
    },
    "profile": {
      "@type": "Person",
      "email": "demo@example.com",
      "url": "https://demo.example.com",
      "image": "https://example.com/demo-avatar.jpg",
      "jobTitle": "Demo Account",
      "bitcoinAddress": "1Demo...",
      "paymail": "demo@example.com"
    }
  }
}

Sigma Auth merges all these fields into the profile returned via /userinfo.

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:

{
  "sub": "did:bitcoin:id:03a1b2c3d4e5f6...",
  "public_key": "03a1b2c3d4e5f6...",
  "profile": {
    "identityKey": "03a1b2c3d4e5f6...",
    "name": "John Doe",
    "description": "Bitcoin developer",
    "image": "https://example.com/avatar.jpg",
    "paymail": "john@example.com",
    "website": "https://johndoe.com",
    "twitter": "@johndoe"
  }
}

Response without BAP Profile

When no BAP profile exists:

{
  "sub": "did:bitcoin:id:03a1b2c3d4e5f6...",
  "public_key": "03a1b2c3d4e5f6...",
  "profile": {
    "identityKey": "03a1b2c3d4e5f6..."
  }
}

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.profile?.name;
  
  if (hasFullProfile) {
    return (
      <div className="profile">
        <img src={userInfo.profile.image} alt="Profile" />
        <h2>{userInfo.profile.name}</h2>
        <p>{userInfo.profile.description}</p>
        {userInfo.profile.paymail && (
          <p>Paymail: {userInfo.profile.paymail}</p>
        )}
      </div>
    );
  }
  
  // Fallback for users without BAP profiles
  return (
    <div className="profile">
      <div className="avatar-placeholder">
        {userInfo.public_key.slice(0, 2)}
      </div>
      <h2>Bitcoin User</h2>
      <p>Public Key: {userInfo.public_key}</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(profile: any): boolean {
  // At minimum, identityKey should always be present
  if (!profile?.identityKey) {
    return false;
  }
  
  // Additional validation for optional fields
  if (profile.name && typeof profile.name !== 'string') {
    return false;
  }
  
  if (profile.image && !isValidURL(profile.image)) {
    return false;
  }
  
  return true;
}

function processUserInfo(userInfo: any) {
  if (!validateBAPProfile(userInfo.profile)) {
    // Handle invalid or missing profile
    return {
      ...userInfo,
      profile: {
        identityKey: userInfo.public_key,
        // Provide sensible defaults
        name: `Bitcoin User`,
        description: 'Bitcoin identity without BAP profile'
      }
    };
  }
  
  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

Standard BAP Fields (schema.org Person)

FieldTypeSchema.org PropertyDescription
identityKeystring-BAP identity key (always present)
namestringPerson.nameDisplay name
descriptionstringPerson.descriptionProfile bio/description
imagestringPerson.imageProfile image URL
emailstringPerson.emailEmail address
urlstringPerson.urlWebsite URL
paymailstring-Paymail address (Bitcoin-specific)
bitcoinAddressstring-Primary Bitcoin (BSV) address

Extended Fields

BAP profiles can include additional schema.org compatible attestations:

{
  "profile": {
    "@type": "Person",
    "identityKey": "03a1b2c3d4e5f6...",
    "name": "John Doe",
    "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"],
    "pgpPublicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----..."
  }
}

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:

{
  "profile": {
    "@type": "Organization",
    "identityKey": "03a1b2c3d4e5f6...",
    "name": "Bitcoin Corp",
    "legalName": "Bitcoin Corporation Inc.",
    "url": "https://bitcoincorp.com",
    "logo": "https://bitcoincorp.com/logo.png",
    "description": "Leading Bitcoin infrastructure provider",
    "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"
    }]
  }
}

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 profile data
    if (!userInfo.profile?.identityKey) {
      console.warn('Invalid or missing BAP profile data');
      // Provide fallback profile
      userInfo.profile = {
        identityKey: userInfo.public_key,
        name: 'Bitcoin User'
      };
    }
    
    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: 'did:bitcoin:id:unknown',
          public_key: 'unknown',
          profile: {
            identityKey: 'unknown',
            name: 'Bitcoin User (Offline)'
          }
        };
      }
      
      // 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.profile?.name || 'Bitcoin User';
const profileImage = userInfo.profile?.image || '/default-avatar.png';

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.public_key}</div>
      
      {/* Enhanced with BAP profile */}
      {userInfo.profile?.name && (
        <h3>{userInfo.profile.name}</h3>
      )}
      
      {userInfo.profile?.image && (
        <img src={userInfo.profile.image} alt="Profile" />
      )}
    </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