feat: support creating secp256k1 instructions with eth address (#15626)
This commit is contained in:
15
web3.js/module.d.ts
vendored
15
web3.js/module.d.ts
vendored
@ -1039,6 +1039,13 @@ declare module '@solana/web3.js' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// === src/secp256k1-program.js ===
|
// === src/secp256k1-program.js ===
|
||||||
|
export type CreateSecp256k1InstructionWithEthAddressParams = {
|
||||||
|
ethAddress: Buffer | Uint8Array | Array<number> | string;
|
||||||
|
message: Buffer | Uint8Array | Array<number>;
|
||||||
|
signature: Buffer | Uint8Array | Array<number>;
|
||||||
|
recoveryId: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type CreateSecp256k1InstructionWithPublicKeyParams = {
|
export type CreateSecp256k1InstructionWithPublicKeyParams = {
|
||||||
publicKey: Buffer | Uint8Array | Array<number>;
|
publicKey: Buffer | Uint8Array | Array<number>;
|
||||||
message: Buffer | Uint8Array | Array<number>;
|
message: Buffer | Uint8Array | Array<number>;
|
||||||
@ -1054,6 +1061,14 @@ declare module '@solana/web3.js' {
|
|||||||
export class Secp256k1Program {
|
export class Secp256k1Program {
|
||||||
static get programId(): PublicKey;
|
static get programId(): PublicKey;
|
||||||
|
|
||||||
|
static publicKeyToEthAddress(
|
||||||
|
publicKey: Buffer | Uint8Array | Array<number>,
|
||||||
|
): Buffer;
|
||||||
|
|
||||||
|
static createInstructionWithEthAddress(
|
||||||
|
params: CreateSecp256k1InstructionWithEthAddressParams,
|
||||||
|
): TransactionInstruction;
|
||||||
|
|
||||||
static createInstructionWithPublicKey(
|
static createInstructionWithPublicKey(
|
||||||
params: CreateSecp256k1InstructionWithPublicKeyParams,
|
params: CreateSecp256k1InstructionWithPublicKeyParams,
|
||||||
): TransactionInstruction;
|
): TransactionInstruction;
|
||||||
|
@ -1045,6 +1045,13 @@ declare module '@solana/web3.js' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// === src/secp256k1-program.js ===
|
// === src/secp256k1-program.js ===
|
||||||
|
declare export type CreateSecp256k1InstructionWithEthAddressParams = {|
|
||||||
|
ethAddress: Buffer | Uint8Array | Array<number> | string,
|
||||||
|
message: Buffer | Uint8Array | Array<number>,
|
||||||
|
signature: Buffer | Uint8Array | Array<number>,
|
||||||
|
recoveryId: number,
|
||||||
|
|};
|
||||||
|
|
||||||
declare export type CreateSecp256k1InstructionWithPublicKeyParams = {|
|
declare export type CreateSecp256k1InstructionWithPublicKeyParams = {|
|
||||||
publicKey: Buffer | Uint8Array | Array<number>,
|
publicKey: Buffer | Uint8Array | Array<number>,
|
||||||
message: Buffer | Uint8Array | Array<number>,
|
message: Buffer | Uint8Array | Array<number>,
|
||||||
@ -1060,6 +1067,14 @@ declare module '@solana/web3.js' {
|
|||||||
declare export class Secp256k1Program {
|
declare export class Secp256k1Program {
|
||||||
static get programId(): PublicKey;
|
static get programId(): PublicKey;
|
||||||
|
|
||||||
|
static publicKeyToEthAddress(
|
||||||
|
publicKey: Buffer | Uint8Array | Array<number>,
|
||||||
|
): Buffer;
|
||||||
|
|
||||||
|
static createInstructionWithEthAddress(
|
||||||
|
params: CreateSecp256k1InstructionWithEthAddressParams,
|
||||||
|
): TransactionInstruction;
|
||||||
|
|
||||||
static createInstructionWithPublicKey(
|
static createInstructionWithPublicKey(
|
||||||
params: CreateSecp256k1InstructionWithPublicKeyParams,
|
params: CreateSecp256k1InstructionWithPublicKeyParams,
|
||||||
): TransactionInstruction;
|
): TransactionInstruction;
|
||||||
|
@ -13,12 +13,12 @@ import {toBuffer} from './util/to-buffer';
|
|||||||
const {publicKeyCreate, ecdsaSign} = secp256k1;
|
const {publicKeyCreate, ecdsaSign} = secp256k1;
|
||||||
|
|
||||||
const PRIVATE_KEY_BYTES = 32;
|
const PRIVATE_KEY_BYTES = 32;
|
||||||
|
const ETHEREUM_ADDRESS_BYTES = 20;
|
||||||
const PUBLIC_KEY_BYTES = 64;
|
const PUBLIC_KEY_BYTES = 64;
|
||||||
const HASHED_PUBKEY_SERIALIZED_SIZE = 20;
|
|
||||||
const SIGNATURE_OFFSETS_SERIALIZED_SIZE = 11;
|
const SIGNATURE_OFFSETS_SERIALIZED_SIZE = 11;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Secp256k1 instruction using a public key params
|
* Params for creating an secp256k1 instruction using a public key
|
||||||
* @typedef {Object} CreateSecp256k1InstructionWithPublicKeyParams
|
* @typedef {Object} CreateSecp256k1InstructionWithPublicKeyParams
|
||||||
* @property {Buffer | Uint8Array | Array<number>} publicKey
|
* @property {Buffer | Uint8Array | Array<number>} publicKey
|
||||||
* @property {Buffer | Uint8Array | Array<number>} message
|
* @property {Buffer | Uint8Array | Array<number>} message
|
||||||
@ -33,7 +33,22 @@ export type CreateSecp256k1InstructionWithPublicKeyParams = {|
|
|||||||
|};
|
|};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Secp256k1 instruction using a private key params
|
* Params for creating an secp256k1 instruction using an Ethereum address
|
||||||
|
* @typedef {Object} CreateSecp256k1InstructionWithEthAddressParams
|
||||||
|
* @property {Buffer | Uint8Array | Array<number>} ethAddress
|
||||||
|
* @property {Buffer | Uint8Array | Array<number>} message
|
||||||
|
* @property {Buffer | Uint8Array | Array<number>} signature
|
||||||
|
* @property {number} recoveryId
|
||||||
|
*/
|
||||||
|
export type CreateSecp256k1InstructionWithEthAddressParams = {|
|
||||||
|
ethAddress: Buffer | Uint8Array | Array<number> | string,
|
||||||
|
message: Buffer | Uint8Array | Array<number>,
|
||||||
|
signature: Buffer | Uint8Array | Array<number>,
|
||||||
|
recoveryId: number,
|
||||||
|
|};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params for creating an secp256k1 instruction using a private key
|
||||||
* @typedef {Object} CreateSecp256k1InstructionWithPrivateKeyParams
|
* @typedef {Object} CreateSecp256k1InstructionWithPrivateKeyParams
|
||||||
* @property {Buffer | Uint8Array | Array<number>} privateKey
|
* @property {Buffer | Uint8Array | Array<number>} privateKey
|
||||||
* @property {Buffer | Uint8Array | Array<number>} message
|
* @property {Buffer | Uint8Array | Array<number>} message
|
||||||
@ -59,32 +74,72 @@ const SECP256K1_INSTRUCTION_LAYOUT = BufferLayout.struct([
|
|||||||
|
|
||||||
export class Secp256k1Program {
|
export class Secp256k1Program {
|
||||||
/**
|
/**
|
||||||
* Public key that identifies the Secp256k program
|
* Public key that identifies the secp256k1 program
|
||||||
*/
|
*/
|
||||||
static get programId(): PublicKey {
|
static get programId(): PublicKey {
|
||||||
return new PublicKey('KeccakSecp256k11111111111111111111111111111');
|
return new PublicKey('KeccakSecp256k11111111111111111111111111111');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a secp256k1 instruction with public key
|
* Construct an Ethereum address from a secp256k1 public key buffer.
|
||||||
|
* @param {Buffer} publicKey a 64 byte secp256k1 public key buffer
|
||||||
|
*/
|
||||||
|
static publicKeyToEthAddress(
|
||||||
|
publicKey: Buffer | Uint8Array | Array<number>,
|
||||||
|
): Buffer {
|
||||||
|
assert(
|
||||||
|
publicKey.length === PUBLIC_KEY_BYTES,
|
||||||
|
`Public key must be ${PUBLIC_KEY_BYTES} bytes but received ${publicKey.length} bytes`,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Buffer.from(keccak_256.update(toBuffer(publicKey)).digest()).slice(
|
||||||
|
-ETHEREUM_ADDRESS_BYTES,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error constructing Ethereum address: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an secp256k1 instruction with a public key. The public key
|
||||||
|
* must be a buffer that is 64 bytes long.
|
||||||
*/
|
*/
|
||||||
static createInstructionWithPublicKey(
|
static createInstructionWithPublicKey(
|
||||||
params: CreateSecp256k1InstructionWithPublicKeyParams,
|
params: CreateSecp256k1InstructionWithPublicKeyParams,
|
||||||
): TransactionInstruction {
|
): TransactionInstruction {
|
||||||
const {publicKey, message, signature, recoveryId} = params;
|
const {publicKey, message, signature, recoveryId} = params;
|
||||||
|
return Secp256k1Program.createInstructionWithEthAddress({
|
||||||
|
ethAddress: Secp256k1Program.publicKeyToEthAddress(publicKey),
|
||||||
|
message,
|
||||||
|
signature,
|
||||||
|
recoveryId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an secp256k1 instruction with an Ethereum address. The address
|
||||||
|
* must be a hex string or a buffer that is 20 bytes long.
|
||||||
|
*/
|
||||||
|
static createInstructionWithEthAddress(
|
||||||
|
params: CreateSecp256k1InstructionWithEthAddressParams,
|
||||||
|
): TransactionInstruction {
|
||||||
|
const {ethAddress: rawAddress, message, signature, recoveryId} = params;
|
||||||
|
|
||||||
|
let ethAddress = rawAddress;
|
||||||
|
if (typeof rawAddress === 'string') {
|
||||||
|
if (rawAddress.startsWith('0x')) {
|
||||||
|
ethAddress = Buffer.from(rawAddress.substr(2), 'hex');
|
||||||
|
} else {
|
||||||
|
ethAddress = Buffer.from(rawAddress, 'hex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
publicKey.length === PUBLIC_KEY_BYTES,
|
ethAddress.length === ETHEREUM_ADDRESS_BYTES,
|
||||||
`Public key must be ${PUBLIC_KEY_BYTES} bytes`,
|
`Address must be ${ETHEREUM_ADDRESS_BYTES} bytes but received ${ethAddress.length} bytes`,
|
||||||
);
|
);
|
||||||
|
|
||||||
let ethAddress;
|
|
||||||
try {
|
|
||||||
ethAddress = constructEthAddress(publicKey);
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Error constructing ethereum public key: ${error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const dataStart = 1 + SIGNATURE_OFFSETS_SERIALIZED_SIZE;
|
const dataStart = 1 + SIGNATURE_OFFSETS_SERIALIZED_SIZE;
|
||||||
const ethAddressOffset = dataStart;
|
const ethAddressOffset = dataStart;
|
||||||
const signatureOffset = dataStart + ethAddress.length;
|
const signatureOffset = dataStart + ethAddress.length;
|
||||||
@ -106,7 +161,7 @@ export class Secp256k1Program {
|
|||||||
messageDataSize: message.length,
|
messageDataSize: message.length,
|
||||||
messageInstructionIndex: 0,
|
messageInstructionIndex: 0,
|
||||||
signature: toBuffer(signature),
|
signature: toBuffer(signature),
|
||||||
ethAddress,
|
ethAddress: toBuffer(ethAddress),
|
||||||
recoveryId,
|
recoveryId,
|
||||||
},
|
},
|
||||||
instructionData,
|
instructionData,
|
||||||
@ -122,7 +177,8 @@ export class Secp256k1Program {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a secp256k1 instruction with private key
|
* Create an secp256k1 instruction with a private key. The private key
|
||||||
|
* must be a buffer that is 32 bytes long.
|
||||||
*/
|
*/
|
||||||
static createInstructionWithPrivateKey(
|
static createInstructionWithPrivateKey(
|
||||||
params: CreateSecp256k1InstructionWithPrivateKeyParams,
|
params: CreateSecp256k1InstructionWithPrivateKeyParams,
|
||||||
@ -131,7 +187,7 @@ export class Secp256k1Program {
|
|||||||
|
|
||||||
assert(
|
assert(
|
||||||
privateKey.length === PRIVATE_KEY_BYTES,
|
privateKey.length === PRIVATE_KEY_BYTES,
|
||||||
`Private key must be ${PRIVATE_KEY_BYTES} bytes`,
|
`Private key must be ${PRIVATE_KEY_BYTES} bytes but received ${privateKey.length} bytes`,
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -152,11 +208,3 @@ export class Secp256k1Program {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function constructEthAddress(
|
|
||||||
publicKey: Buffer | Uint8Array | Array<number>,
|
|
||||||
): Buffer {
|
|
||||||
return Buffer.from(keccak_256.update(toBuffer(publicKey)).digest()).slice(
|
|
||||||
-HASHED_PUBKEY_SERIALIZED_SIZE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
Secp256k1Program,
|
Secp256k1Program,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
import {url} from './url';
|
import {url} from './url';
|
||||||
import {toBuffer} from '../src/util/to-buffer';
|
|
||||||
|
|
||||||
const randomPrivateKey = () => {
|
const randomPrivateKey = () => {
|
||||||
let privateKey;
|
let privateKey;
|
||||||
@ -25,22 +24,70 @@ const randomPrivateKey = () => {
|
|||||||
|
|
||||||
if (process.env.TEST_LIVE) {
|
if (process.env.TEST_LIVE) {
|
||||||
describe('secp256k1', () => {
|
describe('secp256k1', () => {
|
||||||
it('create secp256k1 instruction with public key', async () => {
|
|
||||||
const privateKey = randomPrivateKey();
|
const privateKey = randomPrivateKey();
|
||||||
const publicKey = publicKeyCreate(privateKey, false).slice(1);
|
const publicKey = publicKeyCreate(privateKey, false).slice(1);
|
||||||
const message = Buffer.from('This is a message');
|
const ethAddress = Secp256k1Program.publicKeyToEthAddress(publicKey);
|
||||||
const messageHash = Buffer.from(
|
const from = new Account();
|
||||||
keccak_256.update(toBuffer(message)).digest(),
|
|
||||||
);
|
|
||||||
const {signature, recid: recoveryId} = ecdsaSign(messageHash, privateKey);
|
|
||||||
const connection = new Connection(url, 'confirmed');
|
const connection = new Connection(url, 'confirmed');
|
||||||
|
|
||||||
const from = new Account();
|
before(async function () {
|
||||||
await connection.confirmTransaction(
|
await connection.confirmTransaction(
|
||||||
await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL),
|
await connection.requestAirdrop(from.publicKey, 10 * LAMPORTS_PER_SOL),
|
||||||
'confirmed',
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create secp256k1 instruction with string address', async () => {
|
||||||
|
const message = Buffer.from('string address');
|
||||||
|
const messageHash = Buffer.from(keccak_256.update(message).digest());
|
||||||
|
const {signature, recid: recoveryId} = ecdsaSign(messageHash, privateKey);
|
||||||
|
const transaction = new Transaction().add(
|
||||||
|
Secp256k1Program.createInstructionWithEthAddress({
|
||||||
|
ethAddress: ethAddress.toString('hex'),
|
||||||
|
message,
|
||||||
|
signature,
|
||||||
|
recoveryId,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await sendAndConfirmTransaction(connection, transaction, [from]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create secp256k1 instruction with 0x prefix string address', async () => {
|
||||||
|
const message = Buffer.from('0x string address');
|
||||||
|
const messageHash = Buffer.from(keccak_256.update(message).digest());
|
||||||
|
const {signature, recid: recoveryId} = ecdsaSign(messageHash, privateKey);
|
||||||
|
const transaction = new Transaction().add(
|
||||||
|
Secp256k1Program.createInstructionWithEthAddress({
|
||||||
|
ethAddress: '0x' + ethAddress.toString('hex'),
|
||||||
|
message,
|
||||||
|
signature,
|
||||||
|
recoveryId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await sendAndConfirmTransaction(connection, transaction, [from]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create secp256k1 instruction with buffer address', async () => {
|
||||||
|
const message = Buffer.from('buffer address');
|
||||||
|
const messageHash = Buffer.from(keccak_256.update(message).digest());
|
||||||
|
const {signature, recid: recoveryId} = ecdsaSign(messageHash, privateKey);
|
||||||
|
const transaction = new Transaction().add(
|
||||||
|
Secp256k1Program.createInstructionWithEthAddress({
|
||||||
|
ethAddress,
|
||||||
|
message,
|
||||||
|
signature,
|
||||||
|
recoveryId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await sendAndConfirmTransaction(connection, transaction, [from]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create secp256k1 instruction with public key', async () => {
|
||||||
|
const message = Buffer.from('public key');
|
||||||
|
const messageHash = Buffer.from(keccak_256.update(message).digest());
|
||||||
|
const {signature, recid: recoveryId} = ecdsaSign(messageHash, privateKey);
|
||||||
const transaction = new Transaction().add(
|
const transaction = new Transaction().add(
|
||||||
Secp256k1Program.createInstructionWithPublicKey({
|
Secp256k1Program.createInstructionWithPublicKey({
|
||||||
publicKey,
|
publicKey,
|
||||||
@ -50,33 +97,19 @@ if (process.env.TEST_LIVE) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
await sendAndConfirmTransaction(connection, transaction, [from], {
|
await sendAndConfirmTransaction(connection, transaction, [from]);
|
||||||
commitment: 'confirmed',
|
|
||||||
preflightCommitment: 'confirmed',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('create secp256k1 instruction with private key', async () => {
|
it('create secp256k1 instruction with private key', async () => {
|
||||||
const privateKey = randomPrivateKey();
|
const message = Buffer.from('private key');
|
||||||
const connection = new Connection(url, 'confirmed');
|
|
||||||
|
|
||||||
const from = new Account();
|
|
||||||
await connection.confirmTransaction(
|
|
||||||
await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL),
|
|
||||||
'confirmed',
|
|
||||||
);
|
|
||||||
|
|
||||||
const transaction = new Transaction().add(
|
const transaction = new Transaction().add(
|
||||||
Secp256k1Program.createInstructionWithPrivateKey({
|
Secp256k1Program.createInstructionWithPrivateKey({
|
||||||
privateKey,
|
privateKey,
|
||||||
message: Buffer.from('Test 123'),
|
message,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
await sendAndConfirmTransaction(connection, transaction, [from], {
|
await sendAndConfirmTransaction(connection, transaction, [from]);
|
||||||
commitment: 'confirmed',
|
|
||||||
preflightCommitment: 'confirmed',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user