feat: support for builtin ed25519 program

This commit is contained in:
Sean Young 2021-09-03 08:57:35 +01:00
parent c91519961c
commit ee0b948903
3 changed files with 195 additions and 0 deletions

View File

@ -0,0 +1,140 @@
import {Buffer} from 'buffer';
import * as BufferLayout from '@solana/buffer-layout';
import nacl from 'tweetnacl';
import {Keypair} from './keypair';
import {PublicKey} from './publickey';
import {TransactionInstruction} from './transaction';
import assert from './util/assert';
const PRIVATE_KEY_BYTES = 64;
const PUBLIC_KEY_BYTES = 32;
const SIGNATURE_BYTES = 64;
/**
* Params for creating an ed25519 instruction using a public key
*/
export type CreateEd25519InstructionWithPublicKeyParams = {
publicKey: Uint8Array;
message: Uint8Array;
signature: Uint8Array;
instructionIndex?: number;
};
/**
* Params for creating an ed25519 instruction using a private key
*/
export type CreateEd25519InstructionWithPrivateKeyParams = {
privateKey: Uint8Array;
message: Uint8Array;
instructionIndex?: number;
};
const ED25519_INSTRUCTION_LAYOUT = BufferLayout.struct([
BufferLayout.u8('numSignatures'),
BufferLayout.u8('padding'),
BufferLayout.u16('signatureOffset'),
BufferLayout.u16('signatureInstructionIndex'),
BufferLayout.u16('publicKeyOffset'),
BufferLayout.u16('publicKeyInstructionIndex'),
BufferLayout.u16('messageDataOffset'),
BufferLayout.u16('messageDataSize'),
BufferLayout.u16('messageInstructionIndex'),
]);
export class Ed25519Program {
/**
* @internal
*/
constructor() {}
/**
* Public key that identifies the ed25519 program
*/
static programId: PublicKey = new PublicKey(
'Ed25519SigVerify111111111111111111111111111',
);
/**
* Create an ed25519 instruction with a public key and signature. The
* public key must be a buffer that is 32 bytes long, and the signature
* must be a buffer of 64 bytes.
*/
static createInstructionWithPublicKey(
params: CreateEd25519InstructionWithPublicKeyParams,
): TransactionInstruction {
const {publicKey, message, signature, instructionIndex} = params;
assert(
publicKey.length === PUBLIC_KEY_BYTES,
`Public Key must be ${PUBLIC_KEY_BYTES} bytes but received ${publicKey.length} bytes`,
);
assert(
signature.length === SIGNATURE_BYTES,
`Signature must be ${SIGNATURE_BYTES} bytes but received ${signature.length} bytes`,
);
const publicKeyOffset = ED25519_INSTRUCTION_LAYOUT.span;
const signatureOffset = publicKeyOffset + publicKey.length;
const messageDataOffset = signatureOffset + signature.length;
const numSignatures = 1;
const instructionData = Buffer.alloc(messageDataOffset + message.length);
ED25519_INSTRUCTION_LAYOUT.encode(
{
numSignatures,
padding: 0,
signatureOffset,
signatureInstructionIndex: instructionIndex,
publicKeyOffset,
publicKeyInstructionIndex: instructionIndex,
messageDataOffset,
messageDataSize: message.length,
messageInstructionIndex: instructionIndex,
},
instructionData,
);
instructionData.fill(publicKey, publicKeyOffset);
instructionData.fill(signature, signatureOffset);
instructionData.fill(message, messageDataOffset);
return new TransactionInstruction({
keys: [],
programId: Ed25519Program.programId,
data: instructionData,
});
}
/**
* Create an ed25519 instruction with a private key. The private key
* must be a buffer that is 64 bytes long.
*/
static createInstructionWithPrivateKey(
params: CreateEd25519InstructionWithPrivateKeyParams,
): TransactionInstruction {
const {privateKey, message, instructionIndex} = params;
assert(
privateKey.length === PRIVATE_KEY_BYTES,
`Private key must be ${PRIVATE_KEY_BYTES} bytes but received ${privateKey.length} bytes`,
);
try {
const keypair = Keypair.fromSecretKey(privateKey);
const publicKey = keypair.publicKey.toBytes();
const signature = nacl.sign.detached(message, keypair.secretKey);
return this.createInstructionWithPublicKey({
publicKey,
message,
signature,
instructionIndex,
});
} catch (error) {
throw new Error(`Error creating instruction; ${error}`);
}
}
}

View File

@ -4,6 +4,7 @@ export * from './bpf-loader-deprecated';
export * from './bpf-loader';
export * from './connection';
export * from './epoch-schedule';
export * from './ed25519-program';
export * from './fee-calculator';
export * from './keypair';
export * from './loader';

View File

@ -0,0 +1,54 @@
import {Buffer} from 'buffer';
import nacl from 'tweetnacl';
import {
Connection,
Keypair,
sendAndConfirmTransaction,
LAMPORTS_PER_SOL,
Transaction,
Ed25519Program,
} from '../src';
import {url} from './url';
if (process.env.TEST_LIVE) {
describe('ed25519', () => {
const keypair = Keypair.generate();
const privateKey = keypair.secretKey;
const publicKey = keypair.publicKey.toBytes();
const from = Keypair.generate();
const connection = new Connection(url, 'confirmed');
before(async function () {
await connection.confirmTransaction(
await connection.requestAirdrop(from.publicKey, 10 * LAMPORTS_PER_SOL),
);
});
it('create ed25519 instruction', async () => {
const message = Buffer.from('string address');
const signature = nacl.sign.detached(message, privateKey);
const transaction = new Transaction().add(
Ed25519Program.createInstructionWithPublicKey({
publicKey,
message,
signature,
}),
);
await sendAndConfirmTransaction(connection, transaction, [from]);
});
it('create ed25519 instruction with private key', async () => {
const message = Buffer.from('private key');
const transaction = new Transaction().add(
Ed25519Program.createInstructionWithPrivateKey({
privateKey,
message,
}),
);
await sendAndConfirmTransaction(connection, transaction, [from]);
});
});
}