-
Notifications
You must be signed in to change notification settings - Fork 6
Description
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