feat: add Secp256k1 support to solana-web3.js (#12958)
* feat: add secp256k1 instruction * feat: use buffer-layout for encoding as well * style: use consistent naming for types * style: update typings and make program functions static * fix: attempt to resolve rollup issue * fix: expose sysvar in typings * fix: remove decode instruction functionality (for now)
This commit is contained in:
4
web3.js/flow-typed/keccak.js
vendored
Normal file
4
web3.js/flow-typed/keccak.js
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module 'keccak' {
|
||||||
|
// TODO: Fill in types
|
||||||
|
declare module.exports: any;
|
||||||
|
}
|
4
web3.js/flow-typed/secp256k1.js
vendored
Normal file
4
web3.js/flow-typed/secp256k1.js
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module 'secp256k1' {
|
||||||
|
// TODO: Fill in types
|
||||||
|
declare module.exports: any;
|
||||||
|
}
|
26
web3.js/module.d.ts
vendored
26
web3.js/module.d.ts
vendored
@ -531,6 +531,7 @@ declare module '@solana/web3.js' {
|
|||||||
export const SYSVAR_RENT_PUBKEY: PublicKey;
|
export const SYSVAR_RENT_PUBKEY: PublicKey;
|
||||||
export const SYSVAR_REWARDS_PUBKEY: PublicKey;
|
export const SYSVAR_REWARDS_PUBKEY: PublicKey;
|
||||||
export const SYSVAR_STAKE_HISTORY_PUBKEY: PublicKey;
|
export const SYSVAR_STAKE_HISTORY_PUBKEY: PublicKey;
|
||||||
|
export const SYSVAR_INSTRUCTIONS_PUBKEY: PublicKey;
|
||||||
|
|
||||||
// === src/vote-account.js ===
|
// === src/vote-account.js ===
|
||||||
export const VOTE_PROGRAM_ID: PublicKey;
|
export const VOTE_PROGRAM_ID: PublicKey;
|
||||||
@ -966,6 +967,31 @@ declare module '@solana/web3.js' {
|
|||||||
): AuthorizeNonceParams;
|
): AuthorizeNonceParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === src/secp256k1-program.js ===
|
||||||
|
export type CreateSecp256k1InstructionWithPublicKeyParams = {
|
||||||
|
publicKey: Buffer | Uint8Array | Array<number>;
|
||||||
|
message: Buffer | Uint8Array | Array<number>;
|
||||||
|
signature: Buffer | Uint8Array | Array<number>;
|
||||||
|
recoveryId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CreateSecp256k1InstructionWithPrivateKeyParams = {
|
||||||
|
privateKey: Buffer | Uint8Array | Array<number>;
|
||||||
|
message: Buffer | Uint8Array | Array<number>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Secp256k1Program {
|
||||||
|
static get programId(): PublicKey;
|
||||||
|
|
||||||
|
static createInstructionWithPublicKey(
|
||||||
|
params: CreateSecp256k1InstructionWithPublicKeyParams,
|
||||||
|
): TransactionInstruction;
|
||||||
|
|
||||||
|
static createInstructionWithPrivateKey(
|
||||||
|
params: CreateSecp256k1InstructionWithPrivateKeyParams,
|
||||||
|
): TransactionInstruction;
|
||||||
|
}
|
||||||
|
|
||||||
// === src/loader.js ===
|
// === src/loader.js ===
|
||||||
export class Loader {
|
export class Loader {
|
||||||
static getMinNumSignatures(dataLength: number): number;
|
static getMinNumSignatures(dataLength: number): number;
|
||||||
|
@ -536,6 +536,7 @@ declare module '@solana/web3.js' {
|
|||||||
declare export var SYSVAR_RENT_PUBKEY;
|
declare export var SYSVAR_RENT_PUBKEY;
|
||||||
declare export var SYSVAR_REWARDS_PUBKEY;
|
declare export var SYSVAR_REWARDS_PUBKEY;
|
||||||
declare export var SYSVAR_STAKE_HISTORY_PUBKEY;
|
declare export var SYSVAR_STAKE_HISTORY_PUBKEY;
|
||||||
|
declare export var SYSVAR_INSTRUCTIONS_PUBKEY;
|
||||||
|
|
||||||
// === src/vote-account.js ===
|
// === src/vote-account.js ===
|
||||||
declare export var VOTE_PROGRAM_ID;
|
declare export var VOTE_PROGRAM_ID;
|
||||||
@ -973,6 +974,31 @@ declare module '@solana/web3.js' {
|
|||||||
): AuthorizeNonceParams;
|
): AuthorizeNonceParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === src/secp256k1-program.js ===
|
||||||
|
declare export type CreateSecp256k1InstructionWithPublicKeyParams = {|
|
||||||
|
publicKey: Buffer | Uint8Array | Array<number>,
|
||||||
|
message: Buffer | Uint8Array | Array<number>,
|
||||||
|
signature: Buffer | Uint8Array | Array<number>,
|
||||||
|
recoveryId: number,
|
||||||
|
|};
|
||||||
|
|
||||||
|
declare export type CreateSecp256k1InstructionWithPrivateKeyParams = {|
|
||||||
|
privateKey: Buffer | Uint8Array | Array<number>,
|
||||||
|
message: Buffer | Uint8Array | Array<number>,
|
||||||
|
|};
|
||||||
|
|
||||||
|
declare export class Secp256k1Program {
|
||||||
|
static get programId(): PublicKey;
|
||||||
|
|
||||||
|
static createInstructionWithPublicKey(
|
||||||
|
params: CreateSecp256k1InstructionWithPublicKeyParams,
|
||||||
|
): TransactionInstruction;
|
||||||
|
|
||||||
|
static createInstructionWithPrivateKey(
|
||||||
|
params: CreateSecp256k1InstructionWithPrivateKeyParams,
|
||||||
|
): TransactionInstruction;
|
||||||
|
}
|
||||||
|
|
||||||
// === src/loader.js ===
|
// === src/loader.js ===
|
||||||
declare export class Loader {
|
declare export class Loader {
|
||||||
static getMinNumSignatures(dataLength: number): number;
|
static getMinNumSignatures(dataLength: number): number;
|
||||||
|
68
web3.js/package-lock.json
generated
68
web3.js/package-lock.json
generated
@ -7163,8 +7163,7 @@
|
|||||||
"brorand": {
|
"brorand": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
|
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"browser-process-hrtime": {
|
"browser-process-hrtime": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@ -11357,7 +11356,6 @@
|
|||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz",
|
||||||
"integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==",
|
"integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "^2.0.3",
|
"inherits": "^2.0.3",
|
||||||
"minimalistic-assert": "^1.0.1"
|
"minimalistic-assert": "^1.0.1"
|
||||||
@ -11380,7 +11378,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||||
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
|
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"hash.js": "^1.0.3",
|
"hash.js": "^1.0.3",
|
||||||
"minimalistic-assert": "^1.0.0",
|
"minimalistic-assert": "^1.0.0",
|
||||||
@ -14453,6 +14450,22 @@
|
|||||||
"verror": "1.10.0"
|
"verror": "1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"keccak": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA==",
|
||||||
|
"requires": {
|
||||||
|
"node-addon-api": "^2.0.0",
|
||||||
|
"node-gyp-build": "^4.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"node-gyp-build": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"keypather": {
|
"keypather": {
|
||||||
"version": "1.10.2",
|
"version": "1.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/keypather/-/keypather-1.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/keypather/-/keypather-1.10.2.tgz",
|
||||||
@ -15461,14 +15474,12 @@
|
|||||||
"minimalistic-assert": {
|
"minimalistic-assert": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
|
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"minimalistic-crypto-utils": {
|
"minimalistic-crypto-utils": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
||||||
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
|
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
@ -15606,6 +15617,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
||||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
|
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
|
||||||
},
|
},
|
||||||
|
"node-addon-api": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA=="
|
||||||
|
},
|
||||||
"node-emoji": {
|
"node-emoji": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz",
|
||||||
@ -21328,6 +21344,42 @@
|
|||||||
"xmlchars": "^2.2.0"
|
"xmlchars": "^2.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"secp256k1": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==",
|
||||||
|
"requires": {
|
||||||
|
"elliptic": "^6.5.2",
|
||||||
|
"node-addon-api": "^2.0.0",
|
||||||
|
"node-gyp-build": "^4.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bn.js": {
|
||||||
|
"version": "4.11.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||||
|
},
|
||||||
|
"elliptic": {
|
||||||
|
"version": "6.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||||
|
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||||
|
"requires": {
|
||||||
|
"bn.js": "^4.4.0",
|
||||||
|
"brorand": "^1.0.1",
|
||||||
|
"hash.js": "^1.0.0",
|
||||||
|
"hmac-drbg": "^1.0.0",
|
||||||
|
"inherits": "^2.0.1",
|
||||||
|
"minimalistic-assert": "^1.0.0",
|
||||||
|
"minimalistic-crypto-utils": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node-gyp-build": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"semantic-release": {
|
"semantic-release": {
|
||||||
"version": "17.2.1",
|
"version": "17.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.2.1.tgz",
|
||||||
|
@ -81,10 +81,12 @@
|
|||||||
"crypto-hash": "^1.2.2",
|
"crypto-hash": "^1.2.2",
|
||||||
"esdoc-inject-style-plugin": "^1.0.0",
|
"esdoc-inject-style-plugin": "^1.0.0",
|
||||||
"jayson": "^3.0.1",
|
"jayson": "^3.0.1",
|
||||||
|
"keccak": "^3.0.1",
|
||||||
"mz": "^2.7.0",
|
"mz": "^2.7.0",
|
||||||
"node-fetch": "^2.2.0",
|
"node-fetch": "^2.2.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"rpc-websockets": "^7.4.2",
|
"rpc-websockets": "^7.4.2",
|
||||||
|
"secp256k1": "^4.0.2",
|
||||||
"superstruct": "^0.8.3",
|
"superstruct": "^0.8.3",
|
||||||
"tweetnacl": "^1.0.0",
|
"tweetnacl": "^1.0.0",
|
||||||
"ws": "^7.0.0"
|
"ws": "^7.0.0"
|
||||||
|
@ -106,6 +106,8 @@ function generateConfig(configType) {
|
|||||||
'superstruct',
|
'superstruct',
|
||||||
'tweetnacl',
|
'tweetnacl',
|
||||||
'url',
|
'url',
|
||||||
|
'secp256k1',
|
||||||
|
'keccak',
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -21,6 +21,7 @@ export {
|
|||||||
SystemProgram,
|
SystemProgram,
|
||||||
SYSTEM_INSTRUCTION_LAYOUTS,
|
SYSTEM_INSTRUCTION_LAYOUTS,
|
||||||
} from './system-program';
|
} from './system-program';
|
||||||
|
export {Secp256k1Program} from './secp256k1-program';
|
||||||
export {Transaction, TransactionInstruction} from './transaction';
|
export {Transaction, TransactionInstruction} from './transaction';
|
||||||
export {VALIDATOR_INFO_KEY, ValidatorInfo} from './validator-info';
|
export {VALIDATOR_INFO_KEY, ValidatorInfo} from './validator-info';
|
||||||
export {VOTE_PROGRAM_ID, VoteAccount} from './vote-account';
|
export {VOTE_PROGRAM_ID, VoteAccount} from './vote-account';
|
||||||
@ -29,6 +30,7 @@ export {
|
|||||||
SYSVAR_RENT_PUBKEY,
|
SYSVAR_RENT_PUBKEY,
|
||||||
SYSVAR_REWARDS_PUBKEY,
|
SYSVAR_REWARDS_PUBKEY,
|
||||||
SYSVAR_STAKE_HISTORY_PUBKEY,
|
SYSVAR_STAKE_HISTORY_PUBKEY,
|
||||||
|
SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||||
} from './sysvar';
|
} from './sysvar';
|
||||||
export {sendAndConfirmTransaction} from './util/send-and-confirm-transaction';
|
export {sendAndConfirmTransaction} from './util/send-and-confirm-transaction';
|
||||||
export {sendAndConfirmRawTransaction} from './util/send-and-confirm-raw-transaction';
|
export {sendAndConfirmRawTransaction} from './util/send-and-confirm-raw-transaction';
|
||||||
|
162
web3.js/src/secp256k1-program.js
Normal file
162
web3.js/src/secp256k1-program.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import * as BufferLayout from 'buffer-layout';
|
||||||
|
import secp256k1 from 'secp256k1';
|
||||||
|
import createKeccakHash from 'keccak';
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
|
import {PublicKey} from './publickey';
|
||||||
|
import {TransactionInstruction} from './transaction';
|
||||||
|
import {toBuffer} from './util/to-buffer';
|
||||||
|
|
||||||
|
const {publicKeyCreate, ecdsaSign} = secp256k1;
|
||||||
|
|
||||||
|
const PRIVATE_KEY_BYTES = 32;
|
||||||
|
const PUBLIC_KEY_BYTES = 65;
|
||||||
|
const HASHED_PUBKEY_SERIALIZED_SIZE = 20;
|
||||||
|
const SIGNATURE_OFFSETS_SERIALIZED_SIZE = 11;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Secp256k1 instruction using a public key params
|
||||||
|
* @typedef {Object} CreateSecp256k1InstructionWithPublicKeyParams
|
||||||
|
* @property {Buffer | Uint8Array | Array<number>} publicKey
|
||||||
|
* @property {Buffer | Uint8Array | Array<number>} message
|
||||||
|
* @property {Buffer | Uint8Array | Array<number>} signature
|
||||||
|
* @property {number} recoveryId
|
||||||
|
*/
|
||||||
|
export type CreateSecp256k1InstructionWithPublicKeyParams = {|
|
||||||
|
publicKey: Buffer | Uint8Array | Array<number>,
|
||||||
|
message: Buffer | Uint8Array | Array<number>,
|
||||||
|
signature: Buffer | Uint8Array | Array<number>,
|
||||||
|
recoveryId: number,
|
||||||
|
|};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Secp256k1 instruction using a private key params
|
||||||
|
* @typedef {Object} CreateSecp256k1InstructionWithPrivateKeyParams
|
||||||
|
* @property {Buffer | Uint8Array | Array<number>} privateKey
|
||||||
|
* @property {Buffer | Uint8Array | Array<number>} message
|
||||||
|
*/
|
||||||
|
export type CreateSecp256k1InstructionWithPrivateKeyParams = {|
|
||||||
|
privateKey: Buffer | Uint8Array | Array<number>,
|
||||||
|
message: Buffer | Uint8Array | Array<number>,
|
||||||
|
|};
|
||||||
|
|
||||||
|
const SECP256K1_INSTRUCTION_LAYOUT = BufferLayout.struct([
|
||||||
|
BufferLayout.u8('numSignatures'),
|
||||||
|
BufferLayout.u16('signatureOffset'),
|
||||||
|
BufferLayout.u8('signatureInstructionIndex'),
|
||||||
|
BufferLayout.u16('ethAddressOffset'),
|
||||||
|
BufferLayout.u8('ethAddressInstructionIndex'),
|
||||||
|
BufferLayout.u16('messageDataOffset'),
|
||||||
|
BufferLayout.u16('messageDataSize'),
|
||||||
|
BufferLayout.u8('messageInstructionIndex'),
|
||||||
|
BufferLayout.blob(20, 'ethPublicKey'),
|
||||||
|
BufferLayout.blob(64, 'signature'),
|
||||||
|
BufferLayout.u8('recoveryId'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export class Secp256k1Program {
|
||||||
|
/**
|
||||||
|
* Public key that identifies the Secp256k program
|
||||||
|
*/
|
||||||
|
static get programId(): PublicKey {
|
||||||
|
return new PublicKey('KeccakSecp256k11111111111111111111111111111');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a secp256k1 instruction with public key
|
||||||
|
*/
|
||||||
|
static createInstructionWithPublicKey(
|
||||||
|
params: CreateSecp256k1InstructionWithPublicKeyParams,
|
||||||
|
): TransactionInstruction {
|
||||||
|
const {publicKey, message, signature, recoveryId} = params;
|
||||||
|
|
||||||
|
assert(
|
||||||
|
publicKey.length === PUBLIC_KEY_BYTES,
|
||||||
|
`Public key must be ${PUBLIC_KEY_BYTES} bytes`,
|
||||||
|
);
|
||||||
|
|
||||||
|
let ethPublicKey;
|
||||||
|
try {
|
||||||
|
ethPublicKey = constructEthPubkey(publicKey);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error constructing ethereum public key: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataStart = 1 + SIGNATURE_OFFSETS_SERIALIZED_SIZE;
|
||||||
|
const ethAddressOffset = dataStart;
|
||||||
|
const signatureOffset = dataStart + ethPublicKey.length;
|
||||||
|
const messageDataOffset = signatureOffset + signature.length + 1;
|
||||||
|
const numSignatures = 1;
|
||||||
|
|
||||||
|
const instructionData = Buffer.alloc(
|
||||||
|
SECP256K1_INSTRUCTION_LAYOUT.span + message.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
SECP256K1_INSTRUCTION_LAYOUT.encode(
|
||||||
|
{
|
||||||
|
numSignatures: numSignatures,
|
||||||
|
signatureOffset: signatureOffset,
|
||||||
|
signatureInstructionIndex: 0,
|
||||||
|
ethAddressOffset: ethAddressOffset,
|
||||||
|
ethAddressInstructionIndex: 0,
|
||||||
|
messageDataOffset: messageDataOffset,
|
||||||
|
messageDataSize: message.length,
|
||||||
|
messageInstructionIndex: 0,
|
||||||
|
signature: toBuffer(signature),
|
||||||
|
ethPublicKey: ethPublicKey,
|
||||||
|
recoveryId: recoveryId,
|
||||||
|
},
|
||||||
|
instructionData,
|
||||||
|
);
|
||||||
|
|
||||||
|
instructionData.fill(toBuffer(message), SECP256K1_INSTRUCTION_LAYOUT.span);
|
||||||
|
|
||||||
|
return new TransactionInstruction({
|
||||||
|
keys: [],
|
||||||
|
programId: Secp256k1Program.programId,
|
||||||
|
data: instructionData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a secp256k1 instruction with private key
|
||||||
|
*/
|
||||||
|
static createInstructionWithPrivateKey(
|
||||||
|
params: CreateSecp256k1InstructionWithPrivateKeyParams,
|
||||||
|
): TransactionInstruction {
|
||||||
|
const {privateKey, message} = params;
|
||||||
|
|
||||||
|
assert(
|
||||||
|
privateKey.length === PRIVATE_KEY_BYTES,
|
||||||
|
`Private key must be ${PRIVATE_KEY_BYTES} bytes`,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const publicKey = publicKeyCreate(privateKey, false);
|
||||||
|
const messageHash = createKeccakHash('keccak256')
|
||||||
|
.update(toBuffer(message))
|
||||||
|
.digest();
|
||||||
|
const {signature, recid: recoveryId} = ecdsaSign(messageHash, privateKey);
|
||||||
|
|
||||||
|
return this.createInstructionWithPublicKey({
|
||||||
|
publicKey,
|
||||||
|
message,
|
||||||
|
signature,
|
||||||
|
recoveryId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error creating instruction; ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function constructEthPubkey(
|
||||||
|
publicKey: Buffer | Uint8Array | Array<number>,
|
||||||
|
): Buffer {
|
||||||
|
return createKeccakHash('keccak256')
|
||||||
|
.update(toBuffer(publicKey.slice(1))) // throw away leading byte
|
||||||
|
.digest()
|
||||||
|
.slice(-HASHED_PUBKEY_SERIALIZED_SIZE);
|
||||||
|
}
|
@ -20,3 +20,7 @@ export const SYSVAR_REWARDS_PUBKEY = new PublicKey(
|
|||||||
export const SYSVAR_STAKE_HISTORY_PUBKEY = new PublicKey(
|
export const SYSVAR_STAKE_HISTORY_PUBKEY = new PublicKey(
|
||||||
'SysvarStakeHistory1111111111111111111111111',
|
'SysvarStakeHistory1111111111111111111111111',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey(
|
||||||
|
'Sysvar1nstructions1111111111111111111111111',
|
||||||
|
);
|
||||||
|
88
web3.js/test/secp256k1-program.test.js
Normal file
88
web3.js/test/secp256k1-program.test.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import createKeccakHash from 'keccak';
|
||||||
|
import secp256k1 from 'secp256k1';
|
||||||
|
import {randomBytes} from 'crypto';
|
||||||
|
|
||||||
|
import {Secp256k1Program} from '../src/secp256k1-program';
|
||||||
|
import {mockRpcEnabled} from './__mocks__/node-fetch';
|
||||||
|
import {url} from './url';
|
||||||
|
import {
|
||||||
|
Connection,
|
||||||
|
Account,
|
||||||
|
sendAndConfirmTransaction,
|
||||||
|
LAMPORTS_PER_SOL,
|
||||||
|
Transaction,
|
||||||
|
} from '../src';
|
||||||
|
|
||||||
|
const {privateKeyVerify, ecdsaSign, publicKeyCreate} = secp256k1;
|
||||||
|
|
||||||
|
if (!mockRpcEnabled) {
|
||||||
|
jest.setTimeout(20000);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('live create secp256k1 instruction with public key', async () => {
|
||||||
|
if (mockRpcEnabled) {
|
||||||
|
console.log('non-live test skipped');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = Buffer.from('This is a message');
|
||||||
|
|
||||||
|
let privateKey;
|
||||||
|
do {
|
||||||
|
privateKey = randomBytes(32);
|
||||||
|
} while (!privateKeyVerify(privateKey));
|
||||||
|
|
||||||
|
const publicKey = publicKeyCreate(privateKey, false);
|
||||||
|
const messageHash = createKeccakHash('keccak256').update(message).digest();
|
||||||
|
const {signature, recid: recoveryId} = ecdsaSign(messageHash, privateKey);
|
||||||
|
|
||||||
|
const instruction = Secp256k1Program.createInstructionWithPublicKey({
|
||||||
|
publicKey,
|
||||||
|
message,
|
||||||
|
signature,
|
||||||
|
recoveryId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const transaction = new Transaction();
|
||||||
|
transaction.add(instruction);
|
||||||
|
|
||||||
|
const connection = new Connection(url, 'recent');
|
||||||
|
const from = new Account();
|
||||||
|
await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL);
|
||||||
|
|
||||||
|
await sendAndConfirmTransaction(connection, transaction, [from], {
|
||||||
|
commitment: 'single',
|
||||||
|
skipPreflight: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('live create secp256k1 instruction with private key', async () => {
|
||||||
|
if (mockRpcEnabled) {
|
||||||
|
console.log('non-live test skipped');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let privateKey;
|
||||||
|
do {
|
||||||
|
privateKey = randomBytes(32);
|
||||||
|
} while (!privateKeyVerify(privateKey));
|
||||||
|
|
||||||
|
const instruction = Secp256k1Program.createInstructionWithPrivateKey({
|
||||||
|
privateKey,
|
||||||
|
message: Buffer.from('Test 123'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const transaction = new Transaction();
|
||||||
|
transaction.add(instruction);
|
||||||
|
|
||||||
|
const connection = new Connection(url, 'recent');
|
||||||
|
const from = new Account();
|
||||||
|
await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL);
|
||||||
|
|
||||||
|
await sendAndConfirmTransaction(connection, transaction, [from], {
|
||||||
|
commitment: 'single',
|
||||||
|
skipPreflight: true,
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user