Skip to content

Delegated Attestations as authorities or signatures #12

@koolamusic

Description

@koolamusic

Currently exploring two implementations for delegated attestations. either as authorities or via signatures

In rust based blockchains, signatures can easily be compute intensive and slow.
However there is the functional requirement for delegated attestations to have some form of signature. Unlike their EVM some rust based chains already sign transactions, before they are even executed, and in solana PDAs or Program Derived Addresses are a public key that can be used to create delegated authorities via a seed or a combination of variables, "signer address + unique string"

Look at the following code examples to get insight into both options.

Delegated Signatories

pub fn delegated_attest(
    ctx: Context<DelegatedAttestation>,
    attestation_data: AttestationData,
    attester_signature: [u8; 64],
) -> Result<()> {
    let attester_pubkey = ctx.accounts.attester.key;

    // Verify the signature
    let verified = verify_signature(
        &ctx.accounts.instruction_sysvar,
        &attester_pubkey,
        &attestation_data,
        attester_signature,
    )?;
    if !verified {
        return Err(AttestationError::InvalidSignature.into());
    }

    let schema_data = &ctx.accounts.schema_data;
    let attestation = &mut ctx.accounts.attestation;

   internal_attest_function(attestation, schema_data);

    Ok(())
}



fn verify_signature(
    instruction_sysvar: &AccountInfo,
    attester_pubkey: &Pubkey,
    attestation_data: &AttestationData,
    attester_signature: [u8; 64],
) -> Result<()> {
    // Load the instructions from the instruction sysvar
    let instruction_sysvar_data = instruction_sysvar.try_borrow_data()?;
    let current_index = solana_program::sysvar::instructions::load_current_index(&instruction_sysvar_data);
    // The ed25519 instruction should be just before the current instruction
    let ed25519_ix_index = current_index.saturating_sub(1);
    let ed25519_ix = solana_program::sysvar::instructions::load_instruction_at(ed25519_ix_index as usize, &instruction_sysvar_data)?;

    // Check that the program ID is the ed25519 program
    if ed25519_ix.program_id != solana_program::ed25519_program::id() {
        return Err(AttestationError::InvalidSignature.into());
    }

    // Parse the ed25519 instruction data
    let ed25519_instruction = ed25519_program::Ed25519Instruction::unpack(&ed25519_ix.data)
        .map_err(|_| AttestationError::InvalidSignature)?;

    // Verify that the public key matches the attester's public key
    if ed25519_instruction.public_key != attester_pubkey.to_bytes() {
        return Err(AttestationError::InvalidSignature.into());
    }
  // Serialize the attestation data
    let expected_message = attestation_data.try_to_vec().map_err(|_| AttestationError::InvalidSignature)?;

    // Verify that the message matches
    if ed25519_instruction.message != expected_message {
        return Err(AttestationError::InvalidSignature.into());
    }

    // Optionally, verify that the signature matches the one provided
    if ed25519_instruction.signature != attester_signature {
        return Err(AttestationError::InvalidSignature.into());
    }

    // All checks passed
    Ok(())
}

In the above we really on a signature verification mechanism, where a delegator can sign an attestation off-chain and we can verify that they signed that attestation, by confirming the signature on-chain.

This approach is permission less, as any delegator does not have to interact with our system to create authorities. the ubiquity also enables these signature implementation to be relied on across several other processes.


Alternatively, taking into account the quirks of the protocols we're designing for like Stellar and the Solana blockchain, there exist a couple other authentication mechanisms that can be used to create delegates.

In solana for example, we can do this using PDA with a delegate.

pub struct DelegatedAttest<'info> {
    #[account(mut)]
    /// The delegate who is submitting the attestation.
    pub delegate: Signer<'info>,
    /// CHECK: The attester's public key; no data needed.
    pub attester: UncheckedAccount<'info>,
    #[account(
        seeds = [b"delegation", attester.key.as_ref(), delegate.key.as_ref()],
        bump,
        constraint = delegation.attester == attester.key() @ AttestationError::InvalidDelegation,
        constraint = delegation.delegate == delegate.key() @ AttestationError::InvalidDelegation,
        constraint = !delegation.revoked @ AttestationError::InvalidDelegation,
    )]
    /// The Delegation account authorizing the delegate.
    pub delegation: Account<'info, Delegation>,
    /// CHECK: The recipient's public key; no data needed.
    pub recipient: UncheckedAccount<'info>,
    #[account(
        constraint = schema_data.to_account_info().owner == &schema_registry_program.key() @ AttestationError::InvalidSchema,
    )]
    /// The schema data account; must match the schema UID.
    pub schema_data: Account<'info, SchemaData>,
    #[account(
        init,
        payer = delegate,
        space = Attestation::LEN,
        seeds = [b"attestation", schema_data.key().as_ref(), recipient.key.as_ref(), attester.key.as_ref()],
        bump
    )]
    /// The attestation account to be created.
    pub attestation: Account<'info, Attestation>,
    /// The Schema Registry program account for CPI.
    pub schema_registry_program: Program<'info, SchemaRegistry>,
    pub system_program: Program<'info, System>,
}

The key to creating a delegated Authority is in the struct above.
However, a delegate must interact with out protocol to create their first delegate using Program Derived Address.

pub struct CreateDelegation<'info> {
    #[account(mut)]
    /// The attester who is creating the delegation.
    pub attester: Signer<'info>,
    /// CHECK: The delegate's public key; no data needed.
    pub delegate: UncheckedAccount<'info>,
    #[account(
        init,
        payer = attester,
        space = Delegation::LEN,
        seeds = [b"delegation", attester.key.as_ref(), delegate.key.as_ref()],
        bump
    )]
    /// The Delegation account to be created.
    pub delegation: Account<'info, Delegation>,
    pub system_program: Program<'info, System>,
}


pub fn create_delegation(
    ctx: Context<CreateDelegation>,
    expiration_time: Option<i64>,
) -> Result<()> {
    let delegation = &mut ctx.accounts.delegation;
    let current_time = Clock::get()?.unix_timestamp;

    // Ensure expiration time is in the future, if provided
    if let Some(exp_time) = expiration_time {
        if exp_time <= current_time {
            return Err(AttestationError::InvalidExpirationTime.into());
        }
    }

    delegation.attester = ctx.accounts.attester.key();
    delegation.delegate = ctx.accounts.delegate.key();
    delegation.expiration_time = expiration_time;
    delegation.revoked = false;

    Ok(())
}

This approach means, an organization can authority more than one delegate to act on their behalf, a feature that could open up more possibilities, but limited in cryptographic proof, as the promise of attestations, when they are not signed on-chain is that there must be some cryptographic proof about the attestation that we can trust,,, but verify

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions