├── .npm-audit-whitelister.json ├── .prettierrc.json ├── src ├── esm │ ├── index.js │ ├── networks.js │ ├── types.js │ ├── ecpair.js │ └── testecc.js └── cjs │ ├── testecc.d.ts │ ├── index.d.ts │ ├── networks.d.ts │ ├── networks.cjs │ ├── index.cjs │ ├── types.d.ts │ ├── types.cjs │ ├── ecpair.d.ts │ ├── testecc.cjs │ └── ecpair.cjs ├── .mocharc.json ├── .gitignore ├── CONTRIBUTING.md ├── ts_src ├── index.ts ├── types.ts ├── networks.ts ├── testecc.ts └── ecpair.ts ├── tsconfig.cjs.json ├── tsconfig.json ├── test ├── tsconfig.json ├── fixtures │ └── ecpair.json └── ecpair.spec.ts ├── tsconfig.base.json ├── tslint.json ├── LICENSE ├── fixup.cjs ├── .github └── workflows │ └── main_ci.yml ├── README.md └── package.json /.npm-audit-whitelister.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /src/esm/index.js: -------------------------------------------------------------------------------- 1 | export { ECPairFactory as default, ECPairFactory, networks } from './ecpair.js'; 2 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/mocharc.json", 3 | "require": "tsx" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | .nyc_output 4 | npm-debug.log 5 | test/*.js 6 | !test/ts-node-register.js 7 | -------------------------------------------------------------------------------- /src/cjs/testecc.d.ts: -------------------------------------------------------------------------------- 1 | import { TinySecp256k1Interface } from './ecpair'; 2 | export declare function testEcc(ecc: TinySecp256k1Interface): void; 3 | -------------------------------------------------------------------------------- /src/cjs/index.d.ts: -------------------------------------------------------------------------------- 1 | export { ECPairFactory as default, ECPairFactory, type Signer, type SignerAsync, type ECPairAPI, type ECPairInterface, type TinySecp256k1Interface, networks, } from './ecpair'; 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Refer to Bitcoinjs-lib 2 | 3 | [Please refer to bitcoinjs-lib CONTRIBUTING for a guide on how to contribute by clicking here.](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/CONTRIBUTING.md) 4 | -------------------------------------------------------------------------------- /src/cjs/networks.d.ts: -------------------------------------------------------------------------------- 1 | import * as v from 'valibot'; 2 | import { NetworkSchema } from './types'; 3 | export type Network = v.InferOutput; 4 | export declare const bitcoin: Network; 5 | export declare const testnet: Network; 6 | -------------------------------------------------------------------------------- /ts_src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | ECPairFactory as default, 3 | ECPairFactory, 4 | type Signer, 5 | type SignerAsync, 6 | type ECPairAPI, 7 | type ECPairInterface, 8 | type TinySecp256k1Interface, 9 | networks, 10 | } from './ecpair'; 11 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": false, 6 | "esModuleInterop": true, 7 | "outDir": "src/cjs", 8 | "module": "commonjs" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "src/esm", 5 | "esModuleInterop": true, 6 | "resolveJsonModule": true, 7 | "module": "ESNext", 8 | "moduleResolution": "Node" 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "NodeNext", 5 | "rootDir": "./", 6 | "moduleResolution": "NodeNext", 7 | "baseUrl": "./", 8 | "resolveJsonModule": true, 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "strict": true, 12 | "skipLibCheck": true 13 | }, 14 | "include": ["**/*.spec.ts"] 15 | } 16 | -------------------------------------------------------------------------------- /src/esm/networks.js: -------------------------------------------------------------------------------- 1 | export const bitcoin = { 2 | messagePrefix: '\x18Bitcoin Signed Message:\n', 3 | bech32: 'bc', 4 | bip32: { 5 | public: 0x0488b21e, 6 | private: 0x0488ade4, 7 | }, 8 | pubKeyHash: 0x00, 9 | scriptHash: 0x05, 10 | wif: 0x80, 11 | }; 12 | export const testnet = { 13 | messagePrefix: '\x18Bitcoin Signed Message:\n', 14 | bech32: 'tb', 15 | bip32: { 16 | public: 0x043587cf, 17 | private: 0x04358394, 18 | }, 19 | pubKeyHash: 0x6f, 20 | scriptHash: 0xc4, 21 | wif: 0xef, 22 | }; 23 | -------------------------------------------------------------------------------- /src/cjs/networks.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | Object.defineProperty(exports, '__esModule', { value: true }); 3 | exports.testnet = exports.bitcoin = void 0; 4 | exports.bitcoin = { 5 | messagePrefix: '\x18Bitcoin Signed Message:\n', 6 | bech32: 'bc', 7 | bip32: { 8 | public: 0x0488b21e, 9 | private: 0x0488ade4, 10 | }, 11 | pubKeyHash: 0x00, 12 | scriptHash: 0x05, 13 | wif: 0x80, 14 | }; 15 | exports.testnet = { 16 | messagePrefix: '\x18Bitcoin Signed Message:\n', 17 | bech32: 'tb', 18 | bip32: { 19 | public: 0x043587cf, 20 | private: 0x04358394, 21 | }, 22 | pubKeyHash: 0x6f, 23 | scriptHash: 0xc4, 24 | wif: 0xef, 25 | }; 26 | -------------------------------------------------------------------------------- /src/cjs/index.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | Object.defineProperty(exports, '__esModule', { value: true }); 3 | exports.networks = exports.ECPairFactory = exports.default = void 0; 4 | var ecpair_1 = require('./ecpair.cjs'); 5 | Object.defineProperty(exports, 'default', { 6 | enumerable: true, 7 | get: function () { 8 | return ecpair_1.ECPairFactory; 9 | }, 10 | }); 11 | Object.defineProperty(exports, 'ECPairFactory', { 12 | enumerable: true, 13 | get: function () { 14 | return ecpair_1.ECPairFactory; 15 | }, 16 | }); 17 | Object.defineProperty(exports, 'networks', { 18 | enumerable: true, 19 | get: function () { 20 | return ecpair_1.networks; 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNEXT", 4 | "module": "commonjs", 5 | "outDir": "./src", 6 | "rootDir": "./ts_src", 7 | "types": ["node"], 8 | "allowJs": false, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "strictFunctionTypes": true, 13 | "strictBindCallApply": true, 14 | "strictPropertyInitialization": true, 15 | "noImplicitThis": true, 16 | "alwaysStrict": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "skipLibCheck": true 20 | }, 21 | "include": ["ts_src/**/*.ts"], 22 | "exclude": ["**/*.spec.ts", "node_modules/**/*"] 23 | } 24 | -------------------------------------------------------------------------------- /src/esm/types.js: -------------------------------------------------------------------------------- 1 | import * as v from 'valibot'; 2 | const Uint32Schema = v.pipe( 3 | v.number(), 4 | v.integer(), 5 | v.minValue(0), 6 | v.maxValue(0xffffffff), 7 | ); 8 | const Uint8Schema = v.pipe( 9 | v.number(), 10 | v.integer(), 11 | v.minValue(0), 12 | v.maxValue(0xff), 13 | ); 14 | export const NetworkSchema = v.object({ 15 | messagePrefix: v.union([v.string(), v.instance(Uint8Array)]), 16 | bech32: v.string(), 17 | bip32: v.object({ 18 | public: Uint32Schema, 19 | private: Uint32Schema, 20 | }), 21 | pubKeyHash: Uint8Schema, 22 | scriptHash: Uint8Schema, 23 | wif: Uint8Schema, 24 | }); 25 | export const Buffer256Bit = v.pipe(v.instance(Uint8Array), v.length(32)); 26 | -------------------------------------------------------------------------------- /ts_src/types.ts: -------------------------------------------------------------------------------- 1 | import * as v from 'valibot'; 2 | 3 | const Uint32Schema = v.pipe( 4 | v.number(), 5 | v.integer(), 6 | v.minValue(0), 7 | v.maxValue(0xffffffff), 8 | ); 9 | 10 | const Uint8Schema = v.pipe( 11 | v.number(), 12 | v.integer(), 13 | v.minValue(0), 14 | v.maxValue(0xff), 15 | ); 16 | 17 | export const NetworkSchema = v.object({ 18 | messagePrefix: v.union([v.string(), v.instance(Uint8Array)]), 19 | bech32: v.string(), 20 | bip32: v.object({ 21 | public: Uint32Schema, 22 | private: Uint32Schema, 23 | }), 24 | pubKeyHash: Uint8Schema, 25 | scriptHash: Uint8Schema, 26 | wif: Uint8Schema, 27 | }); 28 | 29 | export const Buffer256Bit = v.pipe(v.instance(Uint8Array), v.length(32)); 30 | -------------------------------------------------------------------------------- /ts_src/networks.ts: -------------------------------------------------------------------------------- 1 | // https://en.bitcoin.it/wiki/List_of_address_prefixes 2 | import * as v from 'valibot'; 3 | import { NetworkSchema } from './types'; 4 | 5 | export type Network = v.InferOutput; 6 | 7 | export const bitcoin: Network = { 8 | messagePrefix: '\x18Bitcoin Signed Message:\n', 9 | bech32: 'bc', 10 | bip32: { 11 | public: 0x0488b21e, 12 | private: 0x0488ade4, 13 | }, 14 | pubKeyHash: 0x00, 15 | scriptHash: 0x05, 16 | wif: 0x80, 17 | }; 18 | 19 | export const testnet: Network = { 20 | messagePrefix: '\x18Bitcoin Signed Message:\n', 21 | bech32: 'tb', 22 | bip32: { 23 | public: 0x043587cf, 24 | private: 0x04358394, 25 | }, 26 | pubKeyHash: 0x6f, 27 | scriptHash: 0xc4, 28 | wif: 0xef, 29 | }; 30 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "rules": { 5 | "array-type": false, 6 | "arrow-parens": false, 7 | "curly": false, 8 | "indent": [ 9 | true, 10 | "spaces", 11 | 2 12 | ], 13 | "interface-name": [false], 14 | "match-default-export-name": true, 15 | "max-classes-per-file": [false], 16 | "member-access": [true, "no-public"], 17 | "no-bitwise": false, 18 | "no-console": false, 19 | "no-empty": [true, "allow-empty-catch"], 20 | "no-implicit-dependencies": [true, "dev"], 21 | "no-return-await": true, 22 | "no-var-requires": false, 23 | "no-unused-expression": false, 24 | "object-literal-sort-keys": false, 25 | "quotemark": [true, "single", "avoid-escape"], 26 | "typedef": [ 27 | true, 28 | "call-signature", 29 | "property-declaration" 30 | ], 31 | "variable-name": [ 32 | true, 33 | "ban-keywords", 34 | "check-format", 35 | "allow-leading-underscore", 36 | "allow-pascal-case" 37 | ] 38 | }, 39 | "rulesDirectory": [] 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2021 bitcoinjs-lib contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /fixup.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const updateRequires = (filePath) => { 5 | let content = fs.readFileSync(filePath, 'utf8'); 6 | //replace local imports eg. require('./ecpair') to require('ecpair.cjs') 7 | content = content.replace(/require\('\.\/([^']*)'\)/g, "require('./$1.cjs')"); 8 | 9 | fs.writeFileSync(filePath, content, 'utf8'); 10 | }; 11 | 12 | const updateImports = (filePath) => { 13 | let content = fs.readFileSync(filePath, 'utf8'); 14 | //replace local imports eg. from './types'; to from './types.js'; 15 | content = content.replace(/from '\.\/([^']*)'/g, "from './$1.js'"); 16 | 17 | fs.writeFileSync(filePath, content, 'utf8'); 18 | }; 19 | 20 | const processFiles = (dir) => { 21 | fs.readdirSync(dir).forEach((file) => { 22 | const filePath = path.join(dir, file); 23 | if (fs.lstatSync(filePath).isDirectory()) { 24 | processFiles(filePath); 25 | } else if (filePath.endsWith('.cjs')) { 26 | updateRequires(filePath); 27 | } else if (filePath.endsWith('.js')) { 28 | updateImports(filePath); 29 | } 30 | }); 31 | }; 32 | 33 | const dir = path.join(__dirname, 'src'); 34 | processFiles(dir); 35 | -------------------------------------------------------------------------------- /src/cjs/types.d.ts: -------------------------------------------------------------------------------- 1 | import * as v from 'valibot'; 2 | export declare const NetworkSchema: v.ObjectSchema<{ 3 | readonly messagePrefix: v.UnionSchema<[v.StringSchema, v.InstanceSchema], undefined>; 4 | readonly bech32: v.StringSchema; 5 | readonly bip32: v.ObjectSchema<{ 6 | readonly public: v.SchemaWithPipe<[v.NumberSchema, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; 7 | readonly private: v.SchemaWithPipe<[v.NumberSchema, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; 8 | }, undefined>; 9 | readonly pubKeyHash: v.SchemaWithPipe<[v.NumberSchema, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; 10 | readonly scriptHash: v.SchemaWithPipe<[v.NumberSchema, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; 11 | readonly wif: v.SchemaWithPipe<[v.NumberSchema, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; 12 | }, undefined>; 13 | export declare const Buffer256Bit: v.SchemaWithPipe<[v.InstanceSchema, v.LengthAction]>; 14 | -------------------------------------------------------------------------------- /.github/workflows/main_ci.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | unit: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: 20 17 | registry-url: https://registry.npmjs.org/ 18 | - run: npm ci 19 | - run: npm run unit 20 | coverage: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: actions/setup-node@v4 25 | with: 26 | node-version: 20 27 | registry-url: https://registry.npmjs.org/ 28 | - run: npm ci 29 | - run: npm run coverage 30 | format: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v3 34 | - uses: actions/setup-node@v4 35 | with: 36 | node-version: 20 37 | registry-url: https://registry.npmjs.org/ 38 | - run: npm ci 39 | - run: npm run format:ci 40 | gitdiff: 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v3 44 | - uses: actions/setup-node@v4 45 | with: 46 | node-version: 20 47 | registry-url: https://registry.npmjs.org/ 48 | - run: npm ci 49 | - run: npm run gitdiff:ci 50 | lint: 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@v3 54 | - uses: actions/setup-node@v4 55 | with: 56 | node-version: 20 57 | registry-url: https://registry.npmjs.org/ 58 | - run: npm ci 59 | - run: npm run lint 60 | lint-tests: 61 | runs-on: ubuntu-latest 62 | steps: 63 | - uses: actions/checkout@v3 64 | - uses: actions/setup-node@v4 65 | with: 66 | node-version: 20 67 | registry-url: https://registry.npmjs.org/ 68 | - run: npm ci 69 | - run: npm run lint:tests 70 | -------------------------------------------------------------------------------- /src/cjs/types.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var __createBinding = 3 | (this && this.__createBinding) || 4 | (Object.create 5 | ? function (o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | var desc = Object.getOwnPropertyDescriptor(m, k); 8 | if ( 9 | !desc || 10 | ('get' in desc ? !m.__esModule : desc.writable || desc.configurable) 11 | ) { 12 | desc = { 13 | enumerable: true, 14 | get: function () { 15 | return m[k]; 16 | }, 17 | }; 18 | } 19 | Object.defineProperty(o, k2, desc); 20 | } 21 | : function (o, m, k, k2) { 22 | if (k2 === undefined) k2 = k; 23 | o[k2] = m[k]; 24 | }); 25 | var __setModuleDefault = 26 | (this && this.__setModuleDefault) || 27 | (Object.create 28 | ? function (o, v) { 29 | Object.defineProperty(o, 'default', { enumerable: true, value: v }); 30 | } 31 | : function (o, v) { 32 | o['default'] = v; 33 | }); 34 | var __importStar = 35 | (this && this.__importStar) || 36 | function (mod) { 37 | if (mod && mod.__esModule) return mod; 38 | var result = {}; 39 | if (mod != null) 40 | for (var k in mod) 41 | if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) 42 | __createBinding(result, mod, k); 43 | __setModuleDefault(result, mod); 44 | return result; 45 | }; 46 | Object.defineProperty(exports, '__esModule', { value: true }); 47 | exports.Buffer256Bit = exports.NetworkSchema = void 0; 48 | const v = __importStar(require('valibot')); 49 | const Uint32Schema = v.pipe( 50 | v.number(), 51 | v.integer(), 52 | v.minValue(0), 53 | v.maxValue(0xffffffff), 54 | ); 55 | const Uint8Schema = v.pipe( 56 | v.number(), 57 | v.integer(), 58 | v.minValue(0), 59 | v.maxValue(0xff), 60 | ); 61 | exports.NetworkSchema = v.object({ 62 | messagePrefix: v.union([v.string(), v.instance(Uint8Array)]), 63 | bech32: v.string(), 64 | bip32: v.object({ 65 | public: Uint32Schema, 66 | private: Uint32Schema, 67 | }), 68 | pubKeyHash: Uint8Schema, 69 | scriptHash: Uint8Schema, 70 | wif: Uint8Schema, 71 | }); 72 | exports.Buffer256Bit = v.pipe(v.instance(Uint8Array), v.length(32)); 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ecpair 2 | [![Github CI](https://github.com/bitcoinjs/ecpair/actions/workflows/main_ci.yml/badge.svg)](https://github.com/bitcoinjs/ecpair/actions/workflows/main_ci.yml) [![NPM](https://img.shields.io/npm/v/ecpair.svg)](https://www.npmjs.org/package/ecpair) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) 3 | 4 | A library for managing SECP256k1 keypairs written in TypeScript with transpiled JavaScript committed to git. 5 | 6 | **Note** `ECPair`.makeRandom() uses the `crypto.getRandomValues` if there is no custom `rng` function provided. This API currently is still an experimental feature as of Node.js 18.19.0. To work around this you can do one of the following: 7 | 1. Use a polyfill for crypto.getRandomValues() 8 | 2. Use the `--experimental-global-webcrypto` flag when running node.js. 9 | 3. Pass in a custom rng function to generate random values. 10 | 11 | ## Example 12 | 13 | TypeScript 14 | 15 | ``` typescript 16 | import { Signer, SignerAsync, ECPairInterface, ECPairFactory, ECPairAPI, TinySecp256k1Interface } from 'ecpair'; 17 | import * as crypto from 'crypto'; 18 | 19 | // You need to provide the ECC library. The ECC library must implement 20 | // all the methods of the `TinySecp256k1Interface` interface. 21 | const tinysecp: TinySecp256k1Interface = require('tiny-secp256k1'); 22 | const ECPair: ECPairAPI = ECPairFactory(tinysecp); 23 | 24 | // You don't need to explicitly write ECPairInterface, but just to show 25 | // that the keyPair implements the interface this example includes it. 26 | 27 | // From WIF 28 | const keyPair1: ECPairInterface = ECPair.fromWIF('KynD8ZKdViVo5W82oyxvE18BbG6nZPVQ8Td8hYbwU94RmyUALUik'); 29 | // Random private key 30 | const keyPair2 = ECPair.fromPrivateKey(crypto.randomBytes(32)); 31 | // OR (uses randombytes library, compatible with browser) 32 | const keyPair3 = ECPair.makeRandom(); 33 | // OR use your own custom random buffer generator BE CAREFUL!!!! 34 | const customRandomBufferFunc = (size: number): Buffer => crypto.randomBytes(size); 35 | const keyPair4 = ECPair.makeRandom({ rng: customRandomBufferFunc }); 36 | // From pubkey (33 or 65 byte DER format public key) 37 | const keyPair5 = ECPair.fromPublicKey(keyPair1.publicKey); 38 | 39 | // Pass a custom network 40 | const network = {}; // Your custom network object here 41 | ECPair.makeRandom({ network }); 42 | ECPair.fromPrivateKey(crypto.randomBytes(32), { network }); 43 | ECPair.fromPublicKey(keyPair1.publicKey, { network }); 44 | // fromWIF will check the WIF version against the network you pass in 45 | // pass in multiple networks if you are not sure 46 | ECPair.fromWIF('wif key...', network); 47 | const network2 = {}; // Your custom network object here 48 | const network3 = {}; // Your custom network object here 49 | ECPair.fromWIF('wif key...', [network, network2, network3]); 50 | ``` 51 | 52 | ## LICENSE [MIT](LICENSE) 53 | Written and tested by [bitcoinjs-lib](https://github.com/bitcoinjs/bitcoinjs-lib) contributors since 2014. 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecpair", 3 | "version": "3.0.0", 4 | "description": "Client-side Bitcoin JavaScript library ECPair", 5 | "type": "module", 6 | "main": "src/cjs/index.cjs", 7 | "module": "src/esm/index.js", 8 | "types": "src/cjs/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "require": "./src/cjs/index.cjs", 12 | "import": "./src/esm/index.js", 13 | "types": "./src/cjs/index.d.ts" 14 | } 15 | }, 16 | "engines": { 17 | "node": ">=20.0.0" 18 | }, 19 | "keywords": [ 20 | "bitcoinjs", 21 | "bitcoin", 22 | "browserify", 23 | "javascript", 24 | "bitcoinjs" 25 | ], 26 | "scripts": { 27 | "audit": "NPM_AUDIT_IGNORE_DEV=1 NPM_AUDIT_IGNORE_LEVEL=low npm-audit-whitelister .npm-audit-whitelister.json", 28 | "build": "npm run clean && tsc -p ./tsconfig.json && tsc -p tsconfig.cjs.json && npm run formatjs", 29 | "postbuild": "find src/cjs -type f -name \"*.js\" -exec bash -c 'mv \"$0\" \"${0%.js}.cjs\"' {} \\; && chmod +x ./fixup.cjs && node fixup.cjs", 30 | "clean": "rimraf src", 31 | "clean:jstests": "rimraf 'test/**/*.js'", 32 | "coverage-report": "npm run build && npm run nobuild:coverage-report", 33 | "coverage-html": "npm run build && npm run nobuild:coverage-html", 34 | "coverage": "npm run build && npm run nobuild:coverage", 35 | "format": "npm run prettier -- --write", 36 | "formatjs": "npm run prettierjs -- --write", 37 | "format:ci": "npm run prettier -- --check && npm run prettierjs -- --check", 38 | "gitdiff:ci": "npm run build && git diff --exit-code", 39 | "lint": "tslint -p tsconfig.json -c tslint.json", 40 | "lint:tests": "tslint -p test/tsconfig.json -c tslint.json", 41 | "mocha:ts": "mocha --recursive", 42 | "nobuild:coverage-report": "c8 report --reporter=lcov", 43 | "nobuild:coverage-html": "c8 report --reporter=html", 44 | "nobuild:coverage": "c8 --check-coverage --exclude='**/*.cjs' --branches 90 --functions 90 --lines 90 npm run unit", 45 | "nobuild:unit": "npm run mocha:ts -- 'test/*.ts'", 46 | "prettier": "prettier \"ts_src/**/*.ts\" \"test/**/*.ts\" --ignore-path ./.prettierignore", 47 | "prettierjs": "prettier \"src/**/*.js\" --ignore-path ./.prettierignore", 48 | "test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage", 49 | "unit": "npm run build && npm run nobuild:unit" 50 | }, 51 | "repository": { 52 | "type": "git", 53 | "url": "https://github.com/bitcoinjs/ecpair.git" 54 | }, 55 | "files": [ 56 | "src" 57 | ], 58 | "dependencies": { 59 | "uint8array-tools": "^0.0.8", 60 | "valibot": "^0.37.0", 61 | "wif": "^5.0.0" 62 | }, 63 | "devDependencies": { 64 | "@types/create-hash": "^1.2.2", 65 | "@types/mocha": "^5.2.7", 66 | "@types/node": "^20.14.2", 67 | "@types/wif": "^2.0.2", 68 | "c8": "^10.1.2", 69 | "mocha": "^10.7.3", 70 | "prettier": "^3.3.3", 71 | "rimraf": "^2.6.3", 72 | "tiny-secp256k1": "^2.2.3", 73 | "tslint": "^6.1.3", 74 | "tsx": "^4.16.5", 75 | "typescript": "^5.0.4" 76 | }, 77 | "license": "MIT" 78 | } 79 | -------------------------------------------------------------------------------- /src/cjs/ecpair.d.ts: -------------------------------------------------------------------------------- 1 | import { Network } from './networks'; 2 | import * as networks from './networks'; 3 | export { networks }; 4 | import * as v from 'valibot'; 5 | declare const ECPairOptionsSchema: v.OptionalSchema, never>; 7 | readonly network: v.OptionalSchema, v.InstanceSchema], undefined>; 9 | readonly bech32: v.StringSchema; 10 | readonly bip32: v.ObjectSchema<{ 11 | readonly public: v.SchemaWithPipe<[v.NumberSchema, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; 12 | readonly private: v.SchemaWithPipe<[v.NumberSchema, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; 13 | }, undefined>; 14 | readonly pubKeyHash: v.SchemaWithPipe<[v.NumberSchema, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; 15 | readonly scriptHash: v.SchemaWithPipe<[v.NumberSchema, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; 16 | readonly wif: v.SchemaWithPipe<[v.NumberSchema, v.IntegerAction, v.MinValueAction, v.MaxValueAction]>; 17 | }, undefined>, never>; 18 | readonly rng: v.OptionalSchema, v.TransformAction Uint8Array>]>, never>; 19 | }, undefined>, never>; 20 | type ECPairOptions = v.InferOutput; 21 | export interface Signer { 22 | publicKey: Uint8Array; 23 | network?: any; 24 | sign(hash: Uint8Array, lowR?: boolean): Uint8Array; 25 | } 26 | export interface SignerAsync { 27 | publicKey: Uint8Array; 28 | network?: any; 29 | sign(hash: Uint8Array, lowR?: boolean): Promise; 30 | } 31 | export interface ECPairInterface extends Signer { 32 | compressed: boolean; 33 | network: Network; 34 | lowR: boolean; 35 | privateKey?: Uint8Array; 36 | toWIF(): string; 37 | tweak(t: Uint8Array): ECPairInterface; 38 | verify(hash: Uint8Array, signature: Uint8Array): boolean; 39 | verifySchnorr(hash: Uint8Array, signature: Uint8Array): boolean; 40 | signSchnorr(hash: Uint8Array): Uint8Array; 41 | } 42 | export interface ECPairAPI { 43 | isPoint(maybePoint: any): boolean; 44 | fromPrivateKey(buffer: Uint8Array, options?: ECPairOptions): ECPairInterface; 45 | fromPublicKey(buffer: Uint8Array, options?: ECPairOptions): ECPairInterface; 46 | fromWIF(wifString: string, network?: Network | Network[]): ECPairInterface; 47 | makeRandom(options?: ECPairOptions): ECPairInterface; 48 | } 49 | export interface TinySecp256k1Interface { 50 | isPoint(p: Uint8Array): boolean; 51 | pointCompress(p: Uint8Array, compressed?: boolean): Uint8Array; 52 | isPrivate(d: Uint8Array): boolean; 53 | pointFromScalar(d: Uint8Array, compressed?: boolean): Uint8Array | null; 54 | xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null; 55 | privateAdd(d: Uint8Array, tweak: Uint8Array): Uint8Array | null; 56 | privateNegate(d: Uint8Array): Uint8Array; 57 | sign(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array; 58 | signSchnorr?(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array; 59 | verify(h: Uint8Array, Q: Uint8Array, signature: Uint8Array, strict?: boolean): boolean; 60 | verifySchnorr?(h: Uint8Array, Q: Uint8Array, signature: Uint8Array): boolean; 61 | } 62 | interface XOnlyPointAddTweakResult { 63 | parity: 1 | 0; 64 | xOnlyPubkey: Uint8Array; 65 | } 66 | export declare function ECPairFactory(ecc: TinySecp256k1Interface): ECPairAPI; 67 | -------------------------------------------------------------------------------- /test/fixtures/ecpair.json: -------------------------------------------------------------------------------- 1 | { 2 | "valid": [ 3 | { 4 | "d": "0000000000000000000000000000000000000000000000000000000000000001", 5 | "Q": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", 6 | "compressed": true, 7 | "network": "bitcoin", 8 | "address": "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH", 9 | "WIF": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", 10 | "tweak": "KyGComs4RrHnqnn5ku3WxA6CVFmHJV61bm8V36Uu4WMsRjjdeAVr" 11 | }, 12 | { 13 | "d": "0000000000000000000000000000000000000000000000000000000000000001", 14 | "Q": "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", 15 | "compressed": false, 16 | "network": "bitcoin", 17 | "address": "1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm", 18 | "WIF": "5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf", 19 | "tweak": "5JH8fEnnBcdtAhH74buE8WhbmvBwSiFjH2qpb1LTkPWehzrbBaF" 20 | }, 21 | { 22 | "d": "2bfe58ab6d9fd575bdc3a624e4825dd2b375d64ac033fbc46ea79dbab4f69a3e", 23 | "Q": "02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340", 24 | "compressed": true, 25 | "network": "bitcoin", 26 | "address": "1MasfEKgSiaSeri2C6kgznaqBNtyrZPhNq", 27 | "WIF": "KxhEDBQyyEFymvfJD96q8stMbJMbZUb6D1PmXqBWZDU2WvbvVs9o", 28 | "tweak": "KwUyUow4TNzozBuaxR5VAQRw99PFQqYZXFn5ZN7Cm2FPeRvXwoAD" 29 | }, 30 | { 31 | "d": "6c4313b03f2e7324d75e642f0ab81b734b724e13fec930f309e222470236d66b", 32 | "Q": "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34", 33 | "compressed": true, 34 | "network": "bitcoin", 35 | "address": "1LwwMWdSEMHJ2dMhSvAHZ3g95tG2UBv9jg", 36 | "WIF": "KzrA86mCVMGWnLGBQu9yzQa32qbxb5dvSK4XhyjjGAWSBKYX4rHx", 37 | "tweak": "L4LVynF3Ra6tpU14rMjx2bjNTbKpathPA1KcKVch8UpuGJgHakpw" 38 | }, 39 | { 40 | "d": "6c4313b03f2e7324d75e642f0ab81b734b724e13fec930f309e222470236d66b", 41 | "Q": "044289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34cec320a0565fb7caf11b1ca2f445f9b7b012dda5718b3cface369ee3a034ded6", 42 | "compressed": false, 43 | "network": "bitcoin", 44 | "address": "1zXcfvKCLgsFdJDYPuqpu1sF3q92tnnUM", 45 | "WIF": "5JdxzLtFPHNe7CAL8EBC6krdFv9pwPoRo4e3syMZEQT9srmK8hh", 46 | "tweak": "5KRotqK8oMBrMGdievBdwHUj23Lfsj5AYwuFM3rmtYE7rmwTEsf" 47 | }, 48 | { 49 | "d": "6c4313b03f2e7324d75e642f0ab81b734b724e13fec930f309e222470236d66b", 50 | "Q": "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34", 51 | "compressed": true, 52 | "network": "testnet", 53 | "address": "n1TteZiR3NiYojqKAV8fNxtTwsrjM7kVdj", 54 | "WIF": "cRD9b1m3vQxmwmjSoJy7Mj56f4uNFXjcWMCzpQCEmHASS4edEwXv", 55 | "tweak": "cUhVShEtrdo9yuULEmZ5PvES5pdEFLo5E3U5Rv5CdbUuX3pgcvk2" 56 | }, 57 | { 58 | "d": "6c4313b03f2e7324d75e642f0ab81b734b724e13fec930f309e222470236d66b", 59 | "Q": "044289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34cec320a0565fb7caf11b1ca2f445f9b7b012dda5718b3cface369ee3a034ded6", 60 | "compressed": false, 61 | "network": "testnet", 62 | "address": "mgWUuj1J1N882jmqFxtDepEC73Rr22E9GU", 63 | "WIF": "92Qba5hnyWSn5Ffcka56yMQauaWY6ZLd91Vzxbi4a9CCetaHtYj", 64 | "tweak": "93CSUa8gPaFzKL91HG5Yot2gfhhP2tcMttmCRgDHEGyAdoiN1CE" 65 | }, 66 | { 67 | "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", 68 | "Q": "0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", 69 | "compressed": true, 70 | "network": "bitcoin", 71 | "address": "1GrLCmVQXoyJXaPJQdqssNqwxvha1eUo2E", 72 | "WIF": "L5oLkpV3aqBjhki6LmvChTCV6odsp4SXM6FfU2Gppt5kFLaHLuZ9", 73 | "tweak": "KyGComs4RrHnqnn5ku3WxA6CVFmHJV61bm8V36Uu4WMsRjjdeAVr" 74 | } 75 | ], 76 | "invalid": { 77 | "fromPrivateKey": [ 78 | { 79 | "exception": "Private key not in range \\[1, n\\)", 80 | "d": "0000000000000000000000000000000000000000000000000000000000000000" 81 | }, 82 | { 83 | "exception": "Private key not in range \\[1, n\\)", 84 | "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" 85 | }, 86 | { 87 | "exception": "Private key not in range \\[1, n\\)", 88 | "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142" 89 | }, 90 | { 91 | "exception": "ValiError: Invalid type: Expected boolean but received 2", 92 | "d": "0000000000000000000000000000000000000000000000000000000000000001", 93 | "options": { 94 | "compressed": 2 95 | } 96 | }, 97 | { 98 | "exception": "ValiError: Invalid type: Expected string | Uint8Array but received undefined", 99 | "d": "0000000000000000000000000000000000000000000000000000000000000001", 100 | "options": { 101 | "network": {} 102 | } 103 | } 104 | ], 105 | "fromPublicKey": [ 106 | { 107 | "exception": "Error: Point not on the curve", 108 | "Q": "", 109 | "options": {} 110 | }, 111 | { 112 | "exception": "ValiError: Invalid type: Expected string | Uint8Array but received undefined", 113 | "Q": "044289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34cec320a0565fb7caf11b1ca2f445f9b7b012dda5718b3cface369ee3a034ded6", 114 | "options": { 115 | "network": {} 116 | } 117 | }, 118 | { 119 | "description": "Bad X coordinate (== P)", 120 | "exception": "Error: Point not on the curve", 121 | "Q": "040000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", 122 | "options": {} 123 | } 124 | ], 125 | "fromWIF": [ 126 | { 127 | "exception": "Invalid network version", 128 | "network": "bitcoin", 129 | "WIF": "92Qba5hnyWSn5Ffcka56yMQauaWY6ZLd91Vzxbi4a9CCetaHtYj" 130 | }, 131 | { 132 | "exception": "Unknown network version", 133 | "WIF": "brQnSed3Fia1w9VcbbS6ZGDgJ6ENkgwuQY2LS7pEC5bKHD1fMF" 134 | }, 135 | { 136 | "exception": "Invalid compression flag", 137 | "WIF": "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sfZr2ym" 138 | }, 139 | { 140 | "exception": "Invalid WIF length", 141 | "WIF": "3tq8Vmhh9SN5XhjTGSWgx8iKk59XbKG6UH4oqpejRuJhfYD" 142 | }, 143 | { 144 | "exception": "Invalid WIF length", 145 | "WIF": "38uMpGARR2BJy5p4dNFKYg9UsWNoBtkpbdrXDjmfvz8krCtw3T1W92ZDSR" 146 | } 147 | ] 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/esm/ecpair.js: -------------------------------------------------------------------------------- 1 | import * as networks from './networks.js'; 2 | import * as types from './types.js'; 3 | import * as wif from 'wif'; 4 | import { testEcc } from './testecc.js'; 5 | export { networks }; 6 | import * as v from 'valibot'; 7 | import * as tools from 'uint8array-tools'; 8 | const ECPairOptionsSchema = v.optional( 9 | v.object({ 10 | compressed: v.optional(v.boolean()), 11 | network: v.optional(types.NetworkSchema), 12 | // https://github.com/fabian-hiller/valibot/issues/243#issuecomment-2182514063 13 | rng: v.optional( 14 | v.pipe( 15 | v.instance(Function), 16 | v.transform((func) => { 17 | return (arg) => { 18 | const parsedArg = v.parse(v.optional(v.number()), arg); 19 | const returnedValue = func(parsedArg); 20 | const parsedReturn = v.parse(v.instance(Uint8Array), returnedValue); 21 | return parsedReturn; 22 | }; 23 | }), 24 | ), 25 | ), 26 | }), 27 | ); 28 | const toXOnly = (pubKey) => 29 | pubKey.length === 32 ? pubKey : pubKey.subarray(1, 33); 30 | export function ECPairFactory(ecc) { 31 | testEcc(ecc); 32 | function isPoint(maybePoint) { 33 | return ecc.isPoint(maybePoint); 34 | } 35 | function fromPrivateKey(buffer, options) { 36 | v.parse(types.Buffer256Bit, buffer); 37 | if (!ecc.isPrivate(buffer)) 38 | throw new TypeError('Private key not in range [1, n)'); 39 | v.parse(ECPairOptionsSchema, options); 40 | return new ECPair(buffer, undefined, options); 41 | } 42 | function fromPublicKey(buffer, options) { 43 | if (!ecc.isPoint(buffer)) { 44 | throw new Error('Point not on the curve'); 45 | } 46 | v.parse(ECPairOptionsSchema, options); 47 | return new ECPair(undefined, buffer, options); 48 | } 49 | function fromWIF(wifString, network) { 50 | const decoded = wif.decode(wifString); 51 | const version = decoded.version; 52 | // list of networks? 53 | if (Array.isArray(network)) { 54 | network = network 55 | .filter((x) => { 56 | return version === x.wif; 57 | }) 58 | .pop(); 59 | if (!network) throw new Error('Unknown network version'); 60 | // otherwise, assume a network object (or default to bitcoin) 61 | } else { 62 | network = network || networks.bitcoin; 63 | if (version !== network.wif) throw new Error('Invalid network version'); 64 | } 65 | return fromPrivateKey(decoded.privateKey, { 66 | compressed: decoded.compressed, 67 | network: network, 68 | }); 69 | } 70 | /** 71 | * Generates a random ECPairInterface. 72 | * 73 | * Uses `crypto.getRandomValues` under the hood for options.rng function, which is still an experimental feature as of Node.js 18.19.0. To work around this you can do one of the following: 74 | * 1. Use a polyfill for crypto.getRandomValues() 75 | * 2. Use the `--experimental-global-webcrypto` flag when running node.js. 76 | * 3. Pass in a custom rng function to generate random values. 77 | * 78 | * @param {ECPairOptions} options - Options for the ECPairInterface. 79 | * @return {ECPairInterface} A random ECPairInterface. 80 | */ 81 | function makeRandom(options) { 82 | v.parse(ECPairOptionsSchema, options); 83 | if (options === undefined) options = {}; 84 | const rng = 85 | options.rng || ((size) => crypto.getRandomValues(new Uint8Array(size))); 86 | let d; 87 | do { 88 | d = rng(32); 89 | v.parse(types.Buffer256Bit, d); 90 | } while (!ecc.isPrivate(d)); 91 | return fromPrivateKey(d, options); 92 | } 93 | class ECPair { 94 | __D; 95 | __Q; 96 | compressed; 97 | network; 98 | lowR; 99 | constructor(__D, __Q, options) { 100 | this.__D = __D; 101 | this.__Q = __Q; 102 | this.lowR = false; 103 | if (options === undefined) options = {}; 104 | this.compressed = 105 | options.compressed === undefined ? true : options.compressed; 106 | this.network = options.network || networks.bitcoin; 107 | if (__Q !== undefined) this.__Q = ecc.pointCompress(__Q, this.compressed); 108 | } 109 | get privateKey() { 110 | return this.__D; 111 | } 112 | get publicKey() { 113 | if (!this.__Q) { 114 | // It is not possible for both `__Q` and `__D` to be `undefined` at the same time. 115 | // The factory methods guard for this. 116 | const p = ecc.pointFromScalar(this.__D, this.compressed); 117 | // It is not possible for `p` to be null. 118 | // `fromPrivateKey()` checks that `__D` is a valid scalar. 119 | this.__Q = p; 120 | } 121 | return this.__Q; 122 | } 123 | toWIF() { 124 | if (!this.__D) throw new Error('Missing private key'); 125 | return wif.encode({ 126 | compressed: this.compressed, 127 | privateKey: this.__D, 128 | version: this.network.wif, 129 | }); 130 | } 131 | tweak(t) { 132 | if (this.privateKey) return this.tweakFromPrivateKey(t); 133 | return this.tweakFromPublicKey(t); 134 | } 135 | sign(hash, lowR) { 136 | if (!this.__D) throw new Error('Missing private key'); 137 | if (lowR === undefined) lowR = this.lowR; 138 | if (lowR === false) { 139 | return ecc.sign(hash, this.__D); 140 | } else { 141 | let sig = ecc.sign(hash, this.__D); 142 | const extraData = new Uint8Array(32); 143 | let counter = 0; 144 | // if first try is lowR, skip the loop 145 | // for second try and on, add extra entropy counting up 146 | while (sig[0] > 0x7f) { 147 | counter++; 148 | tools.writeUInt32(extraData, 0, counter, 'LE'); 149 | sig = ecc.sign(hash, this.__D, extraData); 150 | } 151 | return sig; 152 | } 153 | } 154 | signSchnorr(hash) { 155 | if (!this.privateKey) throw new Error('Missing private key'); 156 | if (!ecc.signSchnorr) 157 | throw new Error('signSchnorr not supported by ecc library'); 158 | return ecc.signSchnorr(hash, this.privateKey); 159 | } 160 | verify(hash, signature) { 161 | return ecc.verify(hash, this.publicKey, signature); 162 | } 163 | verifySchnorr(hash, signature) { 164 | if (!ecc.verifySchnorr) 165 | throw new Error('verifySchnorr not supported by ecc library'); 166 | return ecc.verifySchnorr(hash, this.publicKey.subarray(1, 33), signature); 167 | } 168 | tweakFromPublicKey(t) { 169 | const xOnlyPubKey = toXOnly(this.publicKey); 170 | const tweakedPublicKey = ecc.xOnlyPointAddTweak(xOnlyPubKey, t); 171 | if (!tweakedPublicKey || tweakedPublicKey.xOnlyPubkey === null) 172 | throw new Error('Cannot tweak public key!'); 173 | const parityByte = Uint8Array.from([ 174 | tweakedPublicKey.parity === 0 ? 0x02 : 0x03, 175 | ]); 176 | return fromPublicKey( 177 | tools.concat([parityByte, tweakedPublicKey.xOnlyPubkey]), 178 | { 179 | network: this.network, 180 | compressed: this.compressed, 181 | }, 182 | ); 183 | } 184 | tweakFromPrivateKey(t) { 185 | const hasOddY = 186 | this.publicKey[0] === 3 || 187 | (this.publicKey[0] === 4 && (this.publicKey[64] & 1) === 1); 188 | const privateKey = hasOddY 189 | ? ecc.privateNegate(this.privateKey) 190 | : this.privateKey; 191 | const tweakedPrivateKey = ecc.privateAdd(privateKey, t); 192 | if (!tweakedPrivateKey) throw new Error('Invalid tweaked private key!'); 193 | return fromPrivateKey(tweakedPrivateKey, { 194 | network: this.network, 195 | compressed: this.compressed, 196 | }); 197 | } 198 | } 199 | return { 200 | isPoint, 201 | fromPrivateKey, 202 | fromPublicKey, 203 | fromWIF, 204 | makeRandom, 205 | }; 206 | } 207 | -------------------------------------------------------------------------------- /src/esm/testecc.js: -------------------------------------------------------------------------------- 1 | const h = (hex) => Buffer.from(hex, 'hex'); 2 | export function testEcc(ecc) { 3 | assert( 4 | ecc.isPoint( 5 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 6 | ), 7 | ); 8 | assert( 9 | !ecc.isPoint( 10 | h('030000000000000000000000000000000000000000000000000000000000000005'), 11 | ), 12 | ); 13 | assert( 14 | ecc.isPrivate( 15 | h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 16 | ), 17 | ); 18 | // order - 1 19 | assert( 20 | ecc.isPrivate( 21 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'), 22 | ), 23 | ); 24 | // 0 25 | assert( 26 | !ecc.isPrivate( 27 | h('0000000000000000000000000000000000000000000000000000000000000000'), 28 | ), 29 | ); 30 | // order 31 | assert( 32 | !ecc.isPrivate( 33 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'), 34 | ), 35 | ); 36 | // order + 1 37 | assert( 38 | !ecc.isPrivate( 39 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142'), 40 | ), 41 | ); 42 | // 1 + 0 == 1 43 | assert( 44 | Buffer.from( 45 | ecc.privateAdd( 46 | h('0000000000000000000000000000000000000000000000000000000000000001'), 47 | h('0000000000000000000000000000000000000000000000000000000000000000'), 48 | ), 49 | ).equals( 50 | h('0000000000000000000000000000000000000000000000000000000000000001'), 51 | ), 52 | ); 53 | // -3 + 3 == 0 54 | assert( 55 | ecc.privateAdd( 56 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413e'), 57 | h('0000000000000000000000000000000000000000000000000000000000000003'), 58 | ) === null, 59 | ); 60 | assert( 61 | Buffer.from( 62 | ecc.privateAdd( 63 | h('e211078564db65c3ce7704f08262b1f38f1ef412ad15b5ac2d76657a63b2c500'), 64 | h('b51fbb69051255d1becbd683de5848242a89c229348dd72896a87ada94ae8665'), 65 | ), 66 | ).equals( 67 | h('9730c2ee69edbb958d42db7460bafa18fef9d955325aec99044c81c8282b0a24'), 68 | ), 69 | ); 70 | assert( 71 | Buffer.from( 72 | ecc.privateNegate( 73 | h('0000000000000000000000000000000000000000000000000000000000000001'), 74 | ), 75 | ).equals( 76 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'), 77 | ), 78 | ); 79 | assert( 80 | Buffer.from( 81 | ecc.privateNegate( 82 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413e'), 83 | ), 84 | ).equals( 85 | h('0000000000000000000000000000000000000000000000000000000000000003'), 86 | ), 87 | ); 88 | assert( 89 | Buffer.from( 90 | ecc.privateNegate( 91 | h('b1121e4088a66a28f5b6b0f5844943ecd9f610196d7bb83b25214b60452c09af'), 92 | ), 93 | ).equals( 94 | h('4eede1bf775995d70a494f0a7bb6bc11e0b8cccd41cce8009ab1132c8b0a3792'), 95 | ), 96 | ); 97 | assert( 98 | Buffer.from( 99 | ecc.pointCompress( 100 | h( 101 | '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', 102 | ), 103 | true, 104 | ), 105 | ).equals( 106 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 107 | ), 108 | ); 109 | assert( 110 | Buffer.from( 111 | ecc.pointCompress( 112 | h( 113 | '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', 114 | ), 115 | false, 116 | ), 117 | ).equals( 118 | h( 119 | '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', 120 | ), 121 | ), 122 | ); 123 | assert( 124 | Buffer.from( 125 | ecc.pointCompress( 126 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 127 | true, 128 | ), 129 | ).equals( 130 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 131 | ), 132 | ); 133 | assert( 134 | Buffer.from( 135 | ecc.pointCompress( 136 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 137 | false, 138 | ), 139 | ).equals( 140 | h( 141 | '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', 142 | ), 143 | ), 144 | ); 145 | assert( 146 | Buffer.from( 147 | ecc.pointFromScalar( 148 | h('b1121e4088a66a28f5b6b0f5844943ecd9f610196d7bb83b25214b60452c09af'), 149 | ), 150 | ).equals( 151 | h('02b07ba9dca9523b7ef4bd97703d43d20399eb698e194704791a25ce77a400df99'), 152 | ), 153 | ); 154 | assert( 155 | ecc.xOnlyPointAddTweak( 156 | h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 157 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'), 158 | ) === null, 159 | ); 160 | let xOnlyRes = ecc.xOnlyPointAddTweak( 161 | h('1617d38ed8d8657da4d4761e8057bc396ea9e4b9d29776d4be096016dbd2509b'), 162 | h('a8397a935f0dfceba6ba9618f6451ef4d80637abf4e6af2669fbc9de6a8fd2ac'), 163 | ); 164 | assert( 165 | Buffer.from(xOnlyRes.xOnlyPubkey).equals( 166 | h('e478f99dab91052ab39a33ea35fd5e6e4933f4d28023cd597c9a1f6760346adf'), 167 | ) && xOnlyRes.parity === 1, 168 | ); 169 | xOnlyRes = ecc.xOnlyPointAddTweak( 170 | h('2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'), 171 | h('823c3cd2142744b075a87eade7e1b8678ba308d566226a0056ca2b7a76f86b47'), 172 | ); 173 | assert( 174 | Buffer.from(xOnlyRes.xOnlyPubkey).equals( 175 | h('9534f8dc8c6deda2dc007655981c78b49c5d96c778fbf363462a11ec9dfd948c'), 176 | ) && xOnlyRes.parity === 0, 177 | ); 178 | assert( 179 | Buffer.from( 180 | ecc.sign( 181 | h('5e9f0a0d593efdcf78ac923bc3313e4e7d408d574354ee2b3288c0da9fbba6ed'), 182 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'), 183 | ), 184 | ).equals( 185 | h( 186 | '54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5', 187 | ), 188 | ), 189 | ); 190 | assert( 191 | ecc.verify( 192 | h('5e9f0a0d593efdcf78ac923bc3313e4e7d408d574354ee2b3288c0da9fbba6ed'), 193 | h('0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 194 | h( 195 | '54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5', 196 | ), 197 | ), 198 | ); 199 | if (ecc.signSchnorr) { 200 | assert( 201 | Buffer.from( 202 | ecc.signSchnorr( 203 | h('7e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c'), 204 | h('c90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b14e5c9'), 205 | h('c87aa53824b4d7ae2eb035a2b5bbbccc080e76cdc6d1692c4b0b62d798e6d906'), 206 | ), 207 | ).equals( 208 | h( 209 | '5831aaeed7b44bb74e5eab94ba9d4294c49bcf2a60728d8b4c200f50dd313c1bab745879a5ad954a72c45a91c3a51d3c7adea98d82f8481e0e1e03674a6f3fb7', 210 | ), 211 | ), 212 | ); 213 | } 214 | if (ecc.verifySchnorr) { 215 | assert( 216 | ecc.verifySchnorr( 217 | h('7e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c'), 218 | h('dd308afec5777e13121fa72b9cc1b7cc0139715309b086c960e18fd969774eb8'), 219 | h( 220 | '5831aaeed7b44bb74e5eab94ba9d4294c49bcf2a60728d8b4c200f50dd313c1bab745879a5ad954a72c45a91c3a51d3c7adea98d82f8481e0e1e03674a6f3fb7', 221 | ), 222 | ), 223 | ); 224 | } 225 | } 226 | function assert(bool) { 227 | if (!bool) throw new Error('ecc library invalid'); 228 | } 229 | -------------------------------------------------------------------------------- /src/cjs/testecc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | Object.defineProperty(exports, '__esModule', { value: true }); 3 | exports.testEcc = testEcc; 4 | const h = (hex) => Buffer.from(hex, 'hex'); 5 | function testEcc(ecc) { 6 | assert( 7 | ecc.isPoint( 8 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 9 | ), 10 | ); 11 | assert( 12 | !ecc.isPoint( 13 | h('030000000000000000000000000000000000000000000000000000000000000005'), 14 | ), 15 | ); 16 | assert( 17 | ecc.isPrivate( 18 | h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 19 | ), 20 | ); 21 | // order - 1 22 | assert( 23 | ecc.isPrivate( 24 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'), 25 | ), 26 | ); 27 | // 0 28 | assert( 29 | !ecc.isPrivate( 30 | h('0000000000000000000000000000000000000000000000000000000000000000'), 31 | ), 32 | ); 33 | // order 34 | assert( 35 | !ecc.isPrivate( 36 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'), 37 | ), 38 | ); 39 | // order + 1 40 | assert( 41 | !ecc.isPrivate( 42 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142'), 43 | ), 44 | ); 45 | // 1 + 0 == 1 46 | assert( 47 | Buffer.from( 48 | ecc.privateAdd( 49 | h('0000000000000000000000000000000000000000000000000000000000000001'), 50 | h('0000000000000000000000000000000000000000000000000000000000000000'), 51 | ), 52 | ).equals( 53 | h('0000000000000000000000000000000000000000000000000000000000000001'), 54 | ), 55 | ); 56 | // -3 + 3 == 0 57 | assert( 58 | ecc.privateAdd( 59 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413e'), 60 | h('0000000000000000000000000000000000000000000000000000000000000003'), 61 | ) === null, 62 | ); 63 | assert( 64 | Buffer.from( 65 | ecc.privateAdd( 66 | h('e211078564db65c3ce7704f08262b1f38f1ef412ad15b5ac2d76657a63b2c500'), 67 | h('b51fbb69051255d1becbd683de5848242a89c229348dd72896a87ada94ae8665'), 68 | ), 69 | ).equals( 70 | h('9730c2ee69edbb958d42db7460bafa18fef9d955325aec99044c81c8282b0a24'), 71 | ), 72 | ); 73 | assert( 74 | Buffer.from( 75 | ecc.privateNegate( 76 | h('0000000000000000000000000000000000000000000000000000000000000001'), 77 | ), 78 | ).equals( 79 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'), 80 | ), 81 | ); 82 | assert( 83 | Buffer.from( 84 | ecc.privateNegate( 85 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413e'), 86 | ), 87 | ).equals( 88 | h('0000000000000000000000000000000000000000000000000000000000000003'), 89 | ), 90 | ); 91 | assert( 92 | Buffer.from( 93 | ecc.privateNegate( 94 | h('b1121e4088a66a28f5b6b0f5844943ecd9f610196d7bb83b25214b60452c09af'), 95 | ), 96 | ).equals( 97 | h('4eede1bf775995d70a494f0a7bb6bc11e0b8cccd41cce8009ab1132c8b0a3792'), 98 | ), 99 | ); 100 | assert( 101 | Buffer.from( 102 | ecc.pointCompress( 103 | h( 104 | '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', 105 | ), 106 | true, 107 | ), 108 | ).equals( 109 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 110 | ), 111 | ); 112 | assert( 113 | Buffer.from( 114 | ecc.pointCompress( 115 | h( 116 | '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', 117 | ), 118 | false, 119 | ), 120 | ).equals( 121 | h( 122 | '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', 123 | ), 124 | ), 125 | ); 126 | assert( 127 | Buffer.from( 128 | ecc.pointCompress( 129 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 130 | true, 131 | ), 132 | ).equals( 133 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 134 | ), 135 | ); 136 | assert( 137 | Buffer.from( 138 | ecc.pointCompress( 139 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 140 | false, 141 | ), 142 | ).equals( 143 | h( 144 | '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', 145 | ), 146 | ), 147 | ); 148 | assert( 149 | Buffer.from( 150 | ecc.pointFromScalar( 151 | h('b1121e4088a66a28f5b6b0f5844943ecd9f610196d7bb83b25214b60452c09af'), 152 | ), 153 | ).equals( 154 | h('02b07ba9dca9523b7ef4bd97703d43d20399eb698e194704791a25ce77a400df99'), 155 | ), 156 | ); 157 | assert( 158 | ecc.xOnlyPointAddTweak( 159 | h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 160 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'), 161 | ) === null, 162 | ); 163 | let xOnlyRes = ecc.xOnlyPointAddTweak( 164 | h('1617d38ed8d8657da4d4761e8057bc396ea9e4b9d29776d4be096016dbd2509b'), 165 | h('a8397a935f0dfceba6ba9618f6451ef4d80637abf4e6af2669fbc9de6a8fd2ac'), 166 | ); 167 | assert( 168 | Buffer.from(xOnlyRes.xOnlyPubkey).equals( 169 | h('e478f99dab91052ab39a33ea35fd5e6e4933f4d28023cd597c9a1f6760346adf'), 170 | ) && xOnlyRes.parity === 1, 171 | ); 172 | xOnlyRes = ecc.xOnlyPointAddTweak( 173 | h('2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'), 174 | h('823c3cd2142744b075a87eade7e1b8678ba308d566226a0056ca2b7a76f86b47'), 175 | ); 176 | assert( 177 | Buffer.from(xOnlyRes.xOnlyPubkey).equals( 178 | h('9534f8dc8c6deda2dc007655981c78b49c5d96c778fbf363462a11ec9dfd948c'), 179 | ) && xOnlyRes.parity === 0, 180 | ); 181 | assert( 182 | Buffer.from( 183 | ecc.sign( 184 | h('5e9f0a0d593efdcf78ac923bc3313e4e7d408d574354ee2b3288c0da9fbba6ed'), 185 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'), 186 | ), 187 | ).equals( 188 | h( 189 | '54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5', 190 | ), 191 | ), 192 | ); 193 | assert( 194 | ecc.verify( 195 | h('5e9f0a0d593efdcf78ac923bc3313e4e7d408d574354ee2b3288c0da9fbba6ed'), 196 | h('0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 197 | h( 198 | '54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5', 199 | ), 200 | ), 201 | ); 202 | if (ecc.signSchnorr) { 203 | assert( 204 | Buffer.from( 205 | ecc.signSchnorr( 206 | h('7e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c'), 207 | h('c90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b14e5c9'), 208 | h('c87aa53824b4d7ae2eb035a2b5bbbccc080e76cdc6d1692c4b0b62d798e6d906'), 209 | ), 210 | ).equals( 211 | h( 212 | '5831aaeed7b44bb74e5eab94ba9d4294c49bcf2a60728d8b4c200f50dd313c1bab745879a5ad954a72c45a91c3a51d3c7adea98d82f8481e0e1e03674a6f3fb7', 213 | ), 214 | ), 215 | ); 216 | } 217 | if (ecc.verifySchnorr) { 218 | assert( 219 | ecc.verifySchnorr( 220 | h('7e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c'), 221 | h('dd308afec5777e13121fa72b9cc1b7cc0139715309b086c960e18fd969774eb8'), 222 | h( 223 | '5831aaeed7b44bb74e5eab94ba9d4294c49bcf2a60728d8b4c200f50dd313c1bab745879a5ad954a72c45a91c3a51d3c7adea98d82f8481e0e1e03674a6f3fb7', 224 | ), 225 | ), 226 | ); 227 | } 228 | } 229 | function assert(bool) { 230 | if (!bool) throw new Error('ecc library invalid'); 231 | } 232 | -------------------------------------------------------------------------------- /ts_src/testecc.ts: -------------------------------------------------------------------------------- 1 | import { TinySecp256k1Interface } from './ecpair'; 2 | 3 | const h = (hex: string): Buffer => Buffer.from(hex, 'hex'); 4 | 5 | export function testEcc(ecc: TinySecp256k1Interface): void { 6 | assert( 7 | ecc.isPoint( 8 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 9 | ), 10 | ); 11 | assert( 12 | !ecc.isPoint( 13 | h('030000000000000000000000000000000000000000000000000000000000000005'), 14 | ), 15 | ); 16 | assert( 17 | ecc.isPrivate( 18 | h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 19 | ), 20 | ); 21 | // order - 1 22 | assert( 23 | ecc.isPrivate( 24 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'), 25 | ), 26 | ); 27 | // 0 28 | assert( 29 | !ecc.isPrivate( 30 | h('0000000000000000000000000000000000000000000000000000000000000000'), 31 | ), 32 | ); 33 | // order 34 | assert( 35 | !ecc.isPrivate( 36 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'), 37 | ), 38 | ); 39 | // order + 1 40 | assert( 41 | !ecc.isPrivate( 42 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142'), 43 | ), 44 | ); 45 | // 1 + 0 == 1 46 | assert( 47 | Buffer.from( 48 | ecc.privateAdd( 49 | h('0000000000000000000000000000000000000000000000000000000000000001'), 50 | h('0000000000000000000000000000000000000000000000000000000000000000'), 51 | )!, 52 | ).equals( 53 | h('0000000000000000000000000000000000000000000000000000000000000001'), 54 | ), 55 | ); 56 | // -3 + 3 == 0 57 | assert( 58 | ecc.privateAdd( 59 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413e'), 60 | h('0000000000000000000000000000000000000000000000000000000000000003'), 61 | ) === null, 62 | ); 63 | assert( 64 | Buffer.from( 65 | ecc.privateAdd( 66 | h('e211078564db65c3ce7704f08262b1f38f1ef412ad15b5ac2d76657a63b2c500'), 67 | h('b51fbb69051255d1becbd683de5848242a89c229348dd72896a87ada94ae8665'), 68 | )!, 69 | ).equals( 70 | h('9730c2ee69edbb958d42db7460bafa18fef9d955325aec99044c81c8282b0a24'), 71 | ), 72 | ); 73 | assert( 74 | Buffer.from( 75 | ecc.privateNegate( 76 | h('0000000000000000000000000000000000000000000000000000000000000001'), 77 | ), 78 | ).equals( 79 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'), 80 | ), 81 | ); 82 | assert( 83 | Buffer.from( 84 | ecc.privateNegate( 85 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413e'), 86 | ), 87 | ).equals( 88 | h('0000000000000000000000000000000000000000000000000000000000000003'), 89 | ), 90 | ); 91 | assert( 92 | Buffer.from( 93 | ecc.privateNegate( 94 | h('b1121e4088a66a28f5b6b0f5844943ecd9f610196d7bb83b25214b60452c09af'), 95 | ), 96 | ).equals( 97 | h('4eede1bf775995d70a494f0a7bb6bc11e0b8cccd41cce8009ab1132c8b0a3792'), 98 | ), 99 | ); 100 | assert( 101 | Buffer.from( 102 | ecc.pointCompress( 103 | h( 104 | '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', 105 | ), 106 | true, 107 | )!, 108 | ).equals( 109 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 110 | ), 111 | ); 112 | assert( 113 | Buffer.from( 114 | ecc.pointCompress( 115 | h( 116 | '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', 117 | ), 118 | false, 119 | )!, 120 | ).equals( 121 | h( 122 | '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', 123 | ), 124 | ), 125 | ); 126 | assert( 127 | Buffer.from( 128 | ecc.pointCompress( 129 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 130 | true, 131 | )!, 132 | ).equals( 133 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 134 | ), 135 | ); 136 | assert( 137 | Buffer.from( 138 | ecc.pointCompress( 139 | h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 140 | false, 141 | )!, 142 | ).equals( 143 | h( 144 | '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8', 145 | ), 146 | ), 147 | ); 148 | assert( 149 | Buffer.from( 150 | ecc.pointFromScalar( 151 | h('b1121e4088a66a28f5b6b0f5844943ecd9f610196d7bb83b25214b60452c09af'), 152 | )!, 153 | ).equals( 154 | h('02b07ba9dca9523b7ef4bd97703d43d20399eb698e194704791a25ce77a400df99'), 155 | ), 156 | ); 157 | 158 | assert( 159 | ecc.xOnlyPointAddTweak( 160 | h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 161 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'), 162 | ) === null, 163 | ); 164 | 165 | let xOnlyRes = ecc.xOnlyPointAddTweak( 166 | h('1617d38ed8d8657da4d4761e8057bc396ea9e4b9d29776d4be096016dbd2509b'), 167 | h('a8397a935f0dfceba6ba9618f6451ef4d80637abf4e6af2669fbc9de6a8fd2ac'), 168 | ); 169 | assert( 170 | Buffer.from(xOnlyRes!.xOnlyPubkey).equals( 171 | h('e478f99dab91052ab39a33ea35fd5e6e4933f4d28023cd597c9a1f6760346adf'), 172 | ) && xOnlyRes!.parity === 1, 173 | ); 174 | 175 | xOnlyRes = ecc.xOnlyPointAddTweak( 176 | h('2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'), 177 | h('823c3cd2142744b075a87eade7e1b8678ba308d566226a0056ca2b7a76f86b47'), 178 | ); 179 | assert( 180 | Buffer.from(xOnlyRes!.xOnlyPubkey).equals( 181 | h('9534f8dc8c6deda2dc007655981c78b49c5d96c778fbf363462a11ec9dfd948c'), 182 | ) && xOnlyRes!.parity === 0, 183 | ); 184 | 185 | assert( 186 | Buffer.from( 187 | ecc.sign( 188 | h('5e9f0a0d593efdcf78ac923bc3313e4e7d408d574354ee2b3288c0da9fbba6ed'), 189 | h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'), 190 | )!, 191 | ).equals( 192 | h( 193 | '54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5', 194 | ), 195 | ), 196 | ); 197 | assert( 198 | ecc.verify( 199 | h('5e9f0a0d593efdcf78ac923bc3313e4e7d408d574354ee2b3288c0da9fbba6ed'), 200 | h('0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), 201 | h( 202 | '54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5', 203 | ), 204 | ), 205 | ); 206 | if (ecc.signSchnorr) { 207 | assert( 208 | Buffer.from( 209 | ecc.signSchnorr( 210 | h('7e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c'), 211 | h('c90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b14e5c9'), 212 | h('c87aa53824b4d7ae2eb035a2b5bbbccc080e76cdc6d1692c4b0b62d798e6d906'), 213 | )!, 214 | ).equals( 215 | h( 216 | '5831aaeed7b44bb74e5eab94ba9d4294c49bcf2a60728d8b4c200f50dd313c1bab745879a5ad954a72c45a91c3a51d3c7adea98d82f8481e0e1e03674a6f3fb7', 217 | ), 218 | ), 219 | ); 220 | } 221 | if (ecc.verifySchnorr) { 222 | assert( 223 | ecc.verifySchnorr( 224 | h('7e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c'), 225 | h('dd308afec5777e13121fa72b9cc1b7cc0139715309b086c960e18fd969774eb8'), 226 | h( 227 | '5831aaeed7b44bb74e5eab94ba9d4294c49bcf2a60728d8b4c200f50dd313c1bab745879a5ad954a72c45a91c3a51d3c7adea98d82f8481e0e1e03674a6f3fb7', 228 | ), 229 | ), 230 | ); 231 | } 232 | } 233 | 234 | function assert(bool: boolean): void { 235 | if (!bool) throw new Error('ecc library invalid'); 236 | } 237 | -------------------------------------------------------------------------------- /src/cjs/ecpair.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var __createBinding = 3 | (this && this.__createBinding) || 4 | (Object.create 5 | ? function (o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | var desc = Object.getOwnPropertyDescriptor(m, k); 8 | if ( 9 | !desc || 10 | ('get' in desc ? !m.__esModule : desc.writable || desc.configurable) 11 | ) { 12 | desc = { 13 | enumerable: true, 14 | get: function () { 15 | return m[k]; 16 | }, 17 | }; 18 | } 19 | Object.defineProperty(o, k2, desc); 20 | } 21 | : function (o, m, k, k2) { 22 | if (k2 === undefined) k2 = k; 23 | o[k2] = m[k]; 24 | }); 25 | var __setModuleDefault = 26 | (this && this.__setModuleDefault) || 27 | (Object.create 28 | ? function (o, v) { 29 | Object.defineProperty(o, 'default', { enumerable: true, value: v }); 30 | } 31 | : function (o, v) { 32 | o['default'] = v; 33 | }); 34 | var __importStar = 35 | (this && this.__importStar) || 36 | function (mod) { 37 | if (mod && mod.__esModule) return mod; 38 | var result = {}; 39 | if (mod != null) 40 | for (var k in mod) 41 | if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) 42 | __createBinding(result, mod, k); 43 | __setModuleDefault(result, mod); 44 | return result; 45 | }; 46 | Object.defineProperty(exports, '__esModule', { value: true }); 47 | exports.networks = void 0; 48 | exports.ECPairFactory = ECPairFactory; 49 | const networks = __importStar(require('./networks.cjs')); 50 | exports.networks = networks; 51 | const types = __importStar(require('./types.cjs')); 52 | const wif = __importStar(require('wif')); 53 | const testecc_1 = require('./testecc.cjs'); 54 | const v = __importStar(require('valibot')); 55 | const tools = __importStar(require('uint8array-tools')); 56 | const ECPairOptionsSchema = v.optional( 57 | v.object({ 58 | compressed: v.optional(v.boolean()), 59 | network: v.optional(types.NetworkSchema), 60 | // https://github.com/fabian-hiller/valibot/issues/243#issuecomment-2182514063 61 | rng: v.optional( 62 | v.pipe( 63 | v.instance(Function), 64 | v.transform((func) => { 65 | return (arg) => { 66 | const parsedArg = v.parse(v.optional(v.number()), arg); 67 | const returnedValue = func(parsedArg); 68 | const parsedReturn = v.parse(v.instance(Uint8Array), returnedValue); 69 | return parsedReturn; 70 | }; 71 | }), 72 | ), 73 | ), 74 | }), 75 | ); 76 | const toXOnly = (pubKey) => 77 | pubKey.length === 32 ? pubKey : pubKey.subarray(1, 33); 78 | function ECPairFactory(ecc) { 79 | (0, testecc_1.testEcc)(ecc); 80 | function isPoint(maybePoint) { 81 | return ecc.isPoint(maybePoint); 82 | } 83 | function fromPrivateKey(buffer, options) { 84 | v.parse(types.Buffer256Bit, buffer); 85 | if (!ecc.isPrivate(buffer)) 86 | throw new TypeError('Private key not in range [1, n)'); 87 | v.parse(ECPairOptionsSchema, options); 88 | return new ECPair(buffer, undefined, options); 89 | } 90 | function fromPublicKey(buffer, options) { 91 | if (!ecc.isPoint(buffer)) { 92 | throw new Error('Point not on the curve'); 93 | } 94 | v.parse(ECPairOptionsSchema, options); 95 | return new ECPair(undefined, buffer, options); 96 | } 97 | function fromWIF(wifString, network) { 98 | const decoded = wif.decode(wifString); 99 | const version = decoded.version; 100 | // list of networks? 101 | if (Array.isArray(network)) { 102 | network = network 103 | .filter((x) => { 104 | return version === x.wif; 105 | }) 106 | .pop(); 107 | if (!network) throw new Error('Unknown network version'); 108 | // otherwise, assume a network object (or default to bitcoin) 109 | } else { 110 | network = network || networks.bitcoin; 111 | if (version !== network.wif) throw new Error('Invalid network version'); 112 | } 113 | return fromPrivateKey(decoded.privateKey, { 114 | compressed: decoded.compressed, 115 | network: network, 116 | }); 117 | } 118 | /** 119 | * Generates a random ECPairInterface. 120 | * 121 | * Uses `crypto.getRandomValues` under the hood for options.rng function, which is still an experimental feature as of Node.js 18.19.0. To work around this you can do one of the following: 122 | * 1. Use a polyfill for crypto.getRandomValues() 123 | * 2. Use the `--experimental-global-webcrypto` flag when running node.js. 124 | * 3. Pass in a custom rng function to generate random values. 125 | * 126 | * @param {ECPairOptions} options - Options for the ECPairInterface. 127 | * @return {ECPairInterface} A random ECPairInterface. 128 | */ 129 | function makeRandom(options) { 130 | v.parse(ECPairOptionsSchema, options); 131 | if (options === undefined) options = {}; 132 | const rng = 133 | options.rng || ((size) => crypto.getRandomValues(new Uint8Array(size))); 134 | let d; 135 | do { 136 | d = rng(32); 137 | v.parse(types.Buffer256Bit, d); 138 | } while (!ecc.isPrivate(d)); 139 | return fromPrivateKey(d, options); 140 | } 141 | class ECPair { 142 | __D; 143 | __Q; 144 | compressed; 145 | network; 146 | lowR; 147 | constructor(__D, __Q, options) { 148 | this.__D = __D; 149 | this.__Q = __Q; 150 | this.lowR = false; 151 | if (options === undefined) options = {}; 152 | this.compressed = 153 | options.compressed === undefined ? true : options.compressed; 154 | this.network = options.network || networks.bitcoin; 155 | if (__Q !== undefined) this.__Q = ecc.pointCompress(__Q, this.compressed); 156 | } 157 | get privateKey() { 158 | return this.__D; 159 | } 160 | get publicKey() { 161 | if (!this.__Q) { 162 | // It is not possible for both `__Q` and `__D` to be `undefined` at the same time. 163 | // The factory methods guard for this. 164 | const p = ecc.pointFromScalar(this.__D, this.compressed); 165 | // It is not possible for `p` to be null. 166 | // `fromPrivateKey()` checks that `__D` is a valid scalar. 167 | this.__Q = p; 168 | } 169 | return this.__Q; 170 | } 171 | toWIF() { 172 | if (!this.__D) throw new Error('Missing private key'); 173 | return wif.encode({ 174 | compressed: this.compressed, 175 | privateKey: this.__D, 176 | version: this.network.wif, 177 | }); 178 | } 179 | tweak(t) { 180 | if (this.privateKey) return this.tweakFromPrivateKey(t); 181 | return this.tweakFromPublicKey(t); 182 | } 183 | sign(hash, lowR) { 184 | if (!this.__D) throw new Error('Missing private key'); 185 | if (lowR === undefined) lowR = this.lowR; 186 | if (lowR === false) { 187 | return ecc.sign(hash, this.__D); 188 | } else { 189 | let sig = ecc.sign(hash, this.__D); 190 | const extraData = new Uint8Array(32); 191 | let counter = 0; 192 | // if first try is lowR, skip the loop 193 | // for second try and on, add extra entropy counting up 194 | while (sig[0] > 0x7f) { 195 | counter++; 196 | tools.writeUInt32(extraData, 0, counter, 'LE'); 197 | sig = ecc.sign(hash, this.__D, extraData); 198 | } 199 | return sig; 200 | } 201 | } 202 | signSchnorr(hash) { 203 | if (!this.privateKey) throw new Error('Missing private key'); 204 | if (!ecc.signSchnorr) 205 | throw new Error('signSchnorr not supported by ecc library'); 206 | return ecc.signSchnorr(hash, this.privateKey); 207 | } 208 | verify(hash, signature) { 209 | return ecc.verify(hash, this.publicKey, signature); 210 | } 211 | verifySchnorr(hash, signature) { 212 | if (!ecc.verifySchnorr) 213 | throw new Error('verifySchnorr not supported by ecc library'); 214 | return ecc.verifySchnorr(hash, this.publicKey.subarray(1, 33), signature); 215 | } 216 | tweakFromPublicKey(t) { 217 | const xOnlyPubKey = toXOnly(this.publicKey); 218 | const tweakedPublicKey = ecc.xOnlyPointAddTweak(xOnlyPubKey, t); 219 | if (!tweakedPublicKey || tweakedPublicKey.xOnlyPubkey === null) 220 | throw new Error('Cannot tweak public key!'); 221 | const parityByte = Uint8Array.from([ 222 | tweakedPublicKey.parity === 0 ? 0x02 : 0x03, 223 | ]); 224 | return fromPublicKey( 225 | tools.concat([parityByte, tweakedPublicKey.xOnlyPubkey]), 226 | { 227 | network: this.network, 228 | compressed: this.compressed, 229 | }, 230 | ); 231 | } 232 | tweakFromPrivateKey(t) { 233 | const hasOddY = 234 | this.publicKey[0] === 3 || 235 | (this.publicKey[0] === 4 && (this.publicKey[64] & 1) === 1); 236 | const privateKey = hasOddY 237 | ? ecc.privateNegate(this.privateKey) 238 | : this.privateKey; 239 | const tweakedPrivateKey = ecc.privateAdd(privateKey, t); 240 | if (!tweakedPrivateKey) throw new Error('Invalid tweaked private key!'); 241 | return fromPrivateKey(tweakedPrivateKey, { 242 | network: this.network, 243 | compressed: this.compressed, 244 | }); 245 | } 246 | } 247 | return { 248 | isPoint, 249 | fromPrivateKey, 250 | fromPublicKey, 251 | fromWIF, 252 | makeRandom, 253 | }; 254 | } 255 | -------------------------------------------------------------------------------- /ts_src/ecpair.ts: -------------------------------------------------------------------------------- 1 | import { Network } from './networks'; 2 | import * as networks from './networks'; 3 | import * as types from './types'; 4 | import * as wif from 'wif'; 5 | import { testEcc } from './testecc'; 6 | export { networks }; 7 | import * as v from 'valibot'; 8 | import * as tools from 'uint8array-tools'; 9 | 10 | const ECPairOptionsSchema = v.optional( 11 | v.object({ 12 | compressed: v.optional(v.boolean()), 13 | network: v.optional(types.NetworkSchema), 14 | // https://github.com/fabian-hiller/valibot/issues/243#issuecomment-2182514063 15 | rng: v.optional( 16 | v.pipe( 17 | v.instance(Function), 18 | v.transform((func) => { 19 | return (arg?: number) => { 20 | const parsedArg = v.parse(v.optional(v.number()), arg); 21 | const returnedValue = func(parsedArg); 22 | const parsedReturn = v.parse(v.instance(Uint8Array), returnedValue); 23 | return parsedReturn; 24 | }; 25 | }), 26 | ), 27 | ), 28 | }), 29 | ); 30 | 31 | type ECPairOptions = v.InferOutput; 32 | 33 | const toXOnly = (pubKey: Uint8Array) => 34 | pubKey.length === 32 ? pubKey : pubKey.subarray(1, 33); 35 | 36 | export interface Signer { 37 | publicKey: Uint8Array; 38 | network?: any; 39 | sign(hash: Uint8Array, lowR?: boolean): Uint8Array; 40 | } 41 | 42 | export interface SignerAsync { 43 | publicKey: Uint8Array; 44 | network?: any; 45 | sign(hash: Uint8Array, lowR?: boolean): Promise; 46 | } 47 | 48 | export interface ECPairInterface extends Signer { 49 | compressed: boolean; 50 | network: Network; 51 | lowR: boolean; 52 | privateKey?: Uint8Array; 53 | toWIF(): string; 54 | tweak(t: Uint8Array): ECPairInterface; 55 | verify(hash: Uint8Array, signature: Uint8Array): boolean; 56 | verifySchnorr(hash: Uint8Array, signature: Uint8Array): boolean; 57 | signSchnorr(hash: Uint8Array): Uint8Array; 58 | } 59 | 60 | export interface ECPairAPI { 61 | isPoint(maybePoint: any): boolean; 62 | fromPrivateKey(buffer: Uint8Array, options?: ECPairOptions): ECPairInterface; 63 | fromPublicKey(buffer: Uint8Array, options?: ECPairOptions): ECPairInterface; 64 | fromWIF(wifString: string, network?: Network | Network[]): ECPairInterface; 65 | makeRandom(options?: ECPairOptions): ECPairInterface; 66 | } 67 | 68 | export interface TinySecp256k1Interface { 69 | isPoint(p: Uint8Array): boolean; 70 | pointCompress(p: Uint8Array, compressed?: boolean): Uint8Array; 71 | isPrivate(d: Uint8Array): boolean; 72 | pointFromScalar(d: Uint8Array, compressed?: boolean): Uint8Array | null; 73 | xOnlyPointAddTweak( 74 | p: Uint8Array, 75 | tweak: Uint8Array, 76 | ): XOnlyPointAddTweakResult | null; 77 | 78 | privateAdd(d: Uint8Array, tweak: Uint8Array): Uint8Array | null; 79 | privateNegate(d: Uint8Array): Uint8Array; 80 | 81 | sign(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array; 82 | signSchnorr?(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array; 83 | 84 | verify( 85 | h: Uint8Array, 86 | Q: Uint8Array, 87 | signature: Uint8Array, 88 | strict?: boolean, 89 | ): boolean; 90 | verifySchnorr?(h: Uint8Array, Q: Uint8Array, signature: Uint8Array): boolean; 91 | } 92 | 93 | interface XOnlyPointAddTweakResult { 94 | parity: 1 | 0; 95 | xOnlyPubkey: Uint8Array; 96 | } 97 | 98 | export function ECPairFactory(ecc: TinySecp256k1Interface): ECPairAPI { 99 | testEcc(ecc); 100 | function isPoint(maybePoint: any): boolean { 101 | return ecc.isPoint(maybePoint); 102 | } 103 | 104 | function fromPrivateKey( 105 | buffer: Uint8Array, 106 | options?: ECPairOptions, 107 | ): ECPairInterface { 108 | v.parse(types.Buffer256Bit, buffer); 109 | if (!ecc.isPrivate(buffer)) 110 | throw new TypeError('Private key not in range [1, n)'); 111 | v.parse(ECPairOptionsSchema, options); 112 | 113 | return new ECPair(buffer, undefined, options); 114 | } 115 | 116 | function fromPublicKey( 117 | buffer: Uint8Array, 118 | options?: ECPairOptions, 119 | ): ECPairInterface { 120 | if (!ecc.isPoint(buffer)) { 121 | throw new Error('Point not on the curve'); 122 | } 123 | v.parse(ECPairOptionsSchema, options); 124 | return new ECPair(undefined, buffer, options); 125 | } 126 | 127 | function fromWIF( 128 | wifString: string, 129 | network?: Network | Network[], 130 | ): ECPairInterface { 131 | const decoded = wif.decode(wifString); 132 | const version = decoded.version; 133 | 134 | // list of networks? 135 | if (Array.isArray(network)) { 136 | network = network 137 | .filter((x: Network) => { 138 | return version === x.wif; 139 | }) 140 | .pop() as Network; 141 | 142 | if (!network) throw new Error('Unknown network version'); 143 | 144 | // otherwise, assume a network object (or default to bitcoin) 145 | } else { 146 | network = network || networks.bitcoin; 147 | 148 | if (version !== (network as Network).wif) 149 | throw new Error('Invalid network version'); 150 | } 151 | 152 | return fromPrivateKey(decoded.privateKey, { 153 | compressed: decoded.compressed, 154 | network: network as Network, 155 | }); 156 | } 157 | 158 | /** 159 | * Generates a random ECPairInterface. 160 | * 161 | * Uses `crypto.getRandomValues` under the hood for options.rng function, which is still an experimental feature as of Node.js 18.19.0. To work around this you can do one of the following: 162 | * 1. Use a polyfill for crypto.getRandomValues() 163 | * 2. Use the `--experimental-global-webcrypto` flag when running node.js. 164 | * 3. Pass in a custom rng function to generate random values. 165 | * 166 | * @param {ECPairOptions} options - Options for the ECPairInterface. 167 | * @return {ECPairInterface} A random ECPairInterface. 168 | */ 169 | function makeRandom(options?: ECPairOptions): ECPairInterface { 170 | v.parse(ECPairOptionsSchema, options); 171 | if (options === undefined) options = {}; 172 | const rng = 173 | options.rng || 174 | ((size: any) => crypto.getRandomValues(new Uint8Array(size))); 175 | 176 | let d; 177 | do { 178 | d = rng(32); 179 | v.parse(types.Buffer256Bit, d); 180 | } while (!ecc.isPrivate(d)); 181 | 182 | return fromPrivateKey(d, options); 183 | } 184 | 185 | class ECPair implements ECPairInterface { 186 | compressed: boolean; 187 | network: Network; 188 | lowR: boolean; 189 | 190 | constructor( 191 | private __D?: Uint8Array, 192 | private __Q?: Uint8Array, 193 | options?: ECPairOptions, 194 | ) { 195 | this.lowR = false; 196 | if (options === undefined) options = {}; 197 | this.compressed = 198 | options.compressed === undefined ? true : options.compressed; 199 | this.network = options.network || networks.bitcoin; 200 | 201 | if (__Q !== undefined) this.__Q = ecc.pointCompress(__Q, this.compressed); 202 | } 203 | 204 | get privateKey(): Uint8Array | undefined { 205 | return this.__D; 206 | } 207 | 208 | get publicKey(): Uint8Array { 209 | if (!this.__Q) { 210 | // It is not possible for both `__Q` and `__D` to be `undefined` at the same time. 211 | // The factory methods guard for this. 212 | const p = ecc.pointFromScalar(this.__D!, this.compressed)!; 213 | // It is not possible for `p` to be null. 214 | // `fromPrivateKey()` checks that `__D` is a valid scalar. 215 | this.__Q = p; 216 | } 217 | 218 | return this.__Q; 219 | } 220 | 221 | toWIF(): string { 222 | if (!this.__D) throw new Error('Missing private key'); 223 | return wif.encode({ 224 | compressed: this.compressed, 225 | privateKey: this.__D, 226 | version: this.network.wif, 227 | }); 228 | } 229 | 230 | tweak(t: Uint8Array): ECPairInterface { 231 | if (this.privateKey) return this.tweakFromPrivateKey(t); 232 | return this.tweakFromPublicKey(t); 233 | } 234 | 235 | sign(hash: Uint8Array, lowR?: boolean): Uint8Array { 236 | if (!this.__D) throw new Error('Missing private key'); 237 | if (lowR === undefined) lowR = this.lowR; 238 | if (lowR === false) { 239 | return ecc.sign(hash, this.__D); 240 | } else { 241 | let sig = ecc.sign(hash, this.__D); 242 | const extraData = new Uint8Array(32); 243 | let counter = 0; 244 | // if first try is lowR, skip the loop 245 | // for second try and on, add extra entropy counting up 246 | while (sig[0] > 0x7f) { 247 | counter++; 248 | tools.writeUInt32(extraData, 0, counter, 'LE'); 249 | sig = ecc.sign(hash, this.__D, extraData); 250 | } 251 | return sig; 252 | } 253 | } 254 | 255 | signSchnorr(hash: Uint8Array): Uint8Array { 256 | if (!this.privateKey) throw new Error('Missing private key'); 257 | if (!ecc.signSchnorr) 258 | throw new Error('signSchnorr not supported by ecc library'); 259 | return ecc.signSchnorr(hash, this.privateKey); 260 | } 261 | 262 | verify(hash: Uint8Array, signature: Uint8Array): boolean { 263 | return ecc.verify(hash, this.publicKey, signature); 264 | } 265 | 266 | verifySchnorr(hash: Uint8Array, signature: Uint8Array): boolean { 267 | if (!ecc.verifySchnorr) 268 | throw new Error('verifySchnorr not supported by ecc library'); 269 | return ecc.verifySchnorr(hash, this.publicKey.subarray(1, 33), signature); 270 | } 271 | 272 | private tweakFromPublicKey(t: Uint8Array): ECPairInterface { 273 | const xOnlyPubKey = toXOnly(this.publicKey); 274 | const tweakedPublicKey = ecc.xOnlyPointAddTweak(xOnlyPubKey, t); 275 | if (!tweakedPublicKey || tweakedPublicKey.xOnlyPubkey === null) 276 | throw new Error('Cannot tweak public key!'); 277 | const parityByte = Uint8Array.from([ 278 | tweakedPublicKey.parity === 0 ? 0x02 : 0x03, 279 | ]); 280 | return fromPublicKey( 281 | tools.concat([parityByte, tweakedPublicKey.xOnlyPubkey]), 282 | { 283 | network: this.network, 284 | compressed: this.compressed, 285 | }, 286 | ); 287 | } 288 | 289 | private tweakFromPrivateKey(t: Uint8Array): ECPairInterface { 290 | const hasOddY = 291 | this.publicKey[0] === 3 || 292 | (this.publicKey[0] === 4 && (this.publicKey[64] & 1) === 1); 293 | const privateKey = hasOddY 294 | ? ecc.privateNegate(this.privateKey!) 295 | : this.privateKey; 296 | 297 | const tweakedPrivateKey = ecc.privateAdd(privateKey!, t); 298 | if (!tweakedPrivateKey) throw new Error('Invalid tweaked private key!'); 299 | 300 | return fromPrivateKey(tweakedPrivateKey, { 301 | network: this.network, 302 | compressed: this.compressed, 303 | }); 304 | } 305 | } 306 | 307 | return { 308 | isPoint, 309 | fromPrivateKey, 310 | fromPublicKey, 311 | fromWIF, 312 | makeRandom, 313 | }; 314 | } 315 | -------------------------------------------------------------------------------- /test/ecpair.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import { createHash } from 'crypto'; 3 | import { beforeEach, describe, it } from 'mocha'; 4 | import { ECPairFactory, networks as NETWORKS } from '..'; 5 | import type { ECPairInterface, TinySecp256k1Interface } from '..'; 6 | import fixtures from './fixtures/ecpair.json'; 7 | import * as tinysecp from 'tiny-secp256k1'; 8 | import * as tools from 'uint8array-tools'; 9 | 10 | const ECPair = ECPairFactory(tinysecp); 11 | 12 | const NETWORKS_LIST = Object.values(NETWORKS); 13 | const ZERO = Buffer.alloc(32, 0); 14 | const ONE = Buffer.from( 15 | '0000000000000000000000000000000000000000000000000000000000000001', 16 | 'hex', 17 | ); 18 | const GROUP_ORDER = Buffer.from( 19 | 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 20 | 'hex', 21 | ); 22 | const GROUP_ORDER_LESS_1 = Buffer.from( 23 | 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', 24 | 'hex', 25 | ); 26 | 27 | function sha256(buffer: Buffer): Buffer { 28 | return createHash('sha256').update(buffer).digest(); 29 | } 30 | 31 | function tapTweakHash(pubKey: Buffer, h?: Buffer): Buffer { 32 | const data = Buffer.concat(h ? [pubKey, h] : [pubKey]); 33 | const tagHash = sha256(Buffer.from('TapTweak')); 34 | const tag = Buffer.concat([tagHash, tagHash]); 35 | return sha256(Buffer.concat([tag, data])); 36 | } 37 | 38 | describe('ECPair', () => { 39 | describe('getPublicKey', () => { 40 | let keyPair: ECPairInterface; 41 | 42 | beforeEach(() => { 43 | keyPair = ECPair.fromPrivateKey(ONE); 44 | }); 45 | 46 | it('calls pointFromScalar lazily', () => { 47 | assert.strictEqual((keyPair as any).__Q, undefined); 48 | 49 | // .publicKey forces the memoization 50 | assert.strictEqual( 51 | tools.toHex(keyPair.publicKey), 52 | '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', 53 | ); 54 | assert.strictEqual( 55 | tools.toHex((keyPair as any).__Q), 56 | '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', 57 | ); 58 | }); 59 | }); 60 | 61 | describe('fromPrivateKey', () => { 62 | it('defaults to compressed', () => { 63 | const keyPair = ECPair.fromPrivateKey(ONE); 64 | 65 | assert.strictEqual(keyPair.compressed, true); 66 | }); 67 | 68 | it('supports the uncompressed option', () => { 69 | const keyPair = ECPair.fromPrivateKey(ONE, { 70 | compressed: false, 71 | }); 72 | 73 | assert.strictEqual(keyPair.compressed, false); 74 | }); 75 | 76 | it('supports the network option', () => { 77 | const keyPair = ECPair.fromPrivateKey(ONE, { 78 | compressed: false, 79 | network: NETWORKS.testnet, 80 | }); 81 | 82 | assert.strictEqual(keyPair.network, NETWORKS.testnet); 83 | }); 84 | 85 | fixtures.valid.forEach((f) => { 86 | it('derives public key for ' + f.WIF, () => { 87 | const d = Buffer.from(f.d, 'hex'); 88 | const keyPair = ECPair.fromPrivateKey(d, { 89 | compressed: f.compressed, 90 | }); 91 | 92 | assert.strictEqual(tools.toHex(keyPair.publicKey), f.Q); 93 | }); 94 | }); 95 | 96 | fixtures.invalid.fromPrivateKey.forEach((f) => { 97 | it('throws ' + f.exception, () => { 98 | const d = Buffer.from(f.d, 'hex'); 99 | assert.throws(() => { 100 | ECPair.fromPrivateKey(d, (f as any).options); 101 | }, new RegExp(f.exception)); 102 | }); 103 | }); 104 | }); 105 | 106 | describe('fromPublicKey', () => { 107 | fixtures.invalid.fromPublicKey.forEach((f) => { 108 | it('throws ' + f.exception, () => { 109 | const Q = Buffer.from(f.Q, 'hex'); 110 | assert.throws(() => { 111 | ECPair.fromPublicKey(Q, (f as any).options); 112 | }, new RegExp(f.exception)); 113 | }); 114 | }); 115 | }); 116 | 117 | describe('fromWIF', () => { 118 | fixtures.valid.forEach((f) => { 119 | it('imports ' + f.WIF + ' (' + f.network + ')', () => { 120 | const network = (NETWORKS as any)[f.network]; 121 | const keyPair = ECPair.fromWIF(f.WIF, network); 122 | 123 | assert.strictEqual(tools.toHex(keyPair.privateKey!), f.d); 124 | assert.strictEqual(keyPair.compressed, f.compressed); 125 | assert.strictEqual(keyPair.network, network); 126 | }); 127 | }); 128 | 129 | fixtures.valid.forEach((f) => { 130 | it('imports ' + f.WIF + ' (via list of networks)', () => { 131 | const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST); 132 | 133 | assert.strictEqual(tools.toHex(keyPair.privateKey!), f.d); 134 | assert.strictEqual(keyPair.compressed, f.compressed); 135 | assert.strictEqual(keyPair.network, (NETWORKS as any)[f.network]); 136 | }); 137 | }); 138 | 139 | fixtures.invalid.fromWIF.forEach((f) => { 140 | it('throws on ' + f.WIF, () => { 141 | assert.throws(() => { 142 | const networks = f.network 143 | ? (NETWORKS as any)[f.network] 144 | : NETWORKS_LIST; 145 | 146 | ECPair.fromWIF(f.WIF, networks); 147 | }, new RegExp(f.exception)); 148 | }); 149 | }); 150 | }); 151 | 152 | describe('toWIF', () => { 153 | fixtures.valid.forEach((f) => { 154 | it('exports ' + f.WIF, () => { 155 | const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST); 156 | const result = keyPair.toWIF(); 157 | assert.strictEqual(result, f.WIF); 158 | }); 159 | }); 160 | it('throws if no private key is found', () => { 161 | assert.throws(() => { 162 | const keyPair = ECPair.makeRandom(); 163 | delete (keyPair as any).__D; 164 | keyPair.toWIF(); 165 | }, /Missing private key/); 166 | }); 167 | it('throws if from public key only', () => { 168 | assert.throws(() => { 169 | const publicKey = Buffer.from( 170 | '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', 171 | 'hex', 172 | ); 173 | const keyPair = ECPair.fromPublicKey(publicKey); 174 | keyPair.toWIF(); 175 | }, /Missing private key/); 176 | }); 177 | }); 178 | 179 | describe('makeRandom', () => { 180 | const d = Buffer.alloc(32, 4); 181 | const exWIF = 'KwMWvwRJeFqxYyhZgNwYuYjbQENDAPAudQx5VEmKJrUZcq6aL2pv'; 182 | 183 | describe('uses crypto.getRandomBytes as RNG', () => { 184 | it('generates a ECPair', () => { 185 | const originalFn = crypto.getRandomValues; 186 | // @ts-ignore 187 | crypto.getRandomValues = (): Buffer => { 188 | return d; 189 | }; 190 | 191 | const keyPair = ECPair.makeRandom(); 192 | assert.strictEqual(keyPair.toWIF(), exWIF); 193 | crypto.getRandomValues = originalFn; 194 | }); 195 | }); 196 | 197 | it('allows a custom RNG to be used', () => { 198 | const keyPair = ECPair.makeRandom({ 199 | rng: (size?: number): Uint8Array => { 200 | return d.slice(0, size); 201 | }, 202 | }); 203 | 204 | assert.strictEqual(keyPair.toWIF(), exWIF); 205 | }); 206 | 207 | it('retains the same defaults as ECPair constructor', () => { 208 | const keyPair = ECPair.makeRandom(); 209 | 210 | assert.strictEqual(keyPair.compressed, true); 211 | assert.strictEqual(keyPair.network, NETWORKS.bitcoin); 212 | }); 213 | 214 | it('supports the options parameter', () => { 215 | const keyPair = ECPair.makeRandom({ 216 | compressed: false, 217 | network: NETWORKS.testnet, 218 | }); 219 | 220 | assert.strictEqual(keyPair.compressed, false); 221 | assert.strictEqual(keyPair.network, NETWORKS.testnet); 222 | }); 223 | 224 | it('throws if d is bad length', () => { 225 | function rng(): Buffer { 226 | return Buffer.alloc(28); 227 | } 228 | 229 | assert.throws(() => { 230 | ECPair.makeRandom({ rng }); 231 | }, /ValiError: Invalid length: Expected 32 but received 28/); 232 | }); 233 | 234 | it('loops until d is within interval [1, n) : 1', () => { 235 | let counter = 0; 236 | const rng = () => { 237 | if (counter++ === 0) return ZERO; 238 | return ONE; 239 | }; 240 | 241 | const keyPair = ECPair.makeRandom({ rng }); 242 | assert.strictEqual(keyPair.privateKey!, ONE); 243 | }); 244 | 245 | it('loops until d is within interval [1, n) : n - 1', () => { 246 | let counter = 0; 247 | const rng = () => { 248 | if (counter++ === 0) return ZERO; // <1 249 | if (counter++ === 1) return GROUP_ORDER; // >n-1 250 | return GROUP_ORDER_LESS_1; // n-1 251 | }; 252 | 253 | const keyPair = ECPair.makeRandom({ rng }); 254 | 255 | assert.strictEqual(keyPair.privateKey!, GROUP_ORDER_LESS_1); 256 | }); 257 | }); 258 | 259 | describe('tweak', () => { 260 | fixtures.valid.forEach((f) => { 261 | it('tweaks private and public key for ' + f.WIF, () => { 262 | const network = (NETWORKS as any)[f.network]; 263 | const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST); 264 | const hash = tapTweakHash(Buffer.from(keyPair.publicKey.slice(1, 33))); 265 | 266 | const tweakedKeyPair = keyPair.tweak(hash); 267 | assert.strictEqual(tweakedKeyPair.toWIF(), f.tweak); 268 | 269 | const Q = Buffer.from(f.Q, 'hex'); 270 | const pubOnlyKeyPair = ECPair.fromPublicKey(Q, { 271 | network, 272 | compressed: f.compressed, 273 | }); 274 | const tweakedPubOnlyKeyPair = pubOnlyKeyPair.tweak(hash); 275 | 276 | assert.deepStrictEqual( 277 | tweakedKeyPair.publicKey, 278 | tweakedPubOnlyKeyPair.publicKey, 279 | ); 280 | }); 281 | }); 282 | }); 283 | 284 | describe('.network', () => { 285 | fixtures.valid.forEach((f) => { 286 | it('returns ' + f.network + ' for ' + f.WIF, () => { 287 | const network = (NETWORKS as any)[f.network]; 288 | const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST); 289 | 290 | assert.strictEqual(keyPair.network, network); 291 | }); 292 | }); 293 | }); 294 | 295 | describe('tinysecp wrappers', () => { 296 | let keyPair: ECPairInterface; 297 | let hash: Buffer; 298 | let signature: Buffer; 299 | 300 | beforeEach(() => { 301 | hash = ZERO; 302 | signature = Buffer.alloc(64, 1); 303 | const mockSign = (h: any, d: any) => { 304 | if (h === hash) { 305 | assert.strictEqual(h, hash); 306 | return signature; 307 | } 308 | return tinysecp.sign(h, d); 309 | }; 310 | 311 | const mockSignSchnorr = (h: any, d: any, e: any) => { 312 | if (h === hash) { 313 | assert.strictEqual(h, hash); 314 | return signature; 315 | } 316 | return tinysecp.signSchnorr(h, d, e); 317 | }; 318 | 319 | const mockVerify = (h: any, Q: any, sig: any) => { 320 | if (h === hash && sig === signature) { 321 | assert.strictEqual(h, hash); 322 | return true; 323 | } 324 | return tinysecp.verify(h, Q, sig); 325 | }; 326 | 327 | const mockVerifySchnorr = (h: any, Q: any, sig: any) => { 328 | if (h === hash && sig === signature) { 329 | assert.strictEqual(h, hash); 330 | return true; 331 | } 332 | return tinysecp.verifySchnorr(h, Q, sig); 333 | }; 334 | 335 | // @ts-ignore 336 | keyPair = ECPairFactory({ 337 | ...tinysecp, 338 | sign: mockSign, 339 | signSchnorr: mockSignSchnorr, 340 | verify: mockVerify, 341 | verifySchnorr: mockVerifySchnorr, 342 | }).makeRandom(); 343 | }); 344 | 345 | describe('signing', () => { 346 | it('wraps tinysecp.sign', () => { 347 | assert.deepStrictEqual(keyPair.sign(hash), signature); 348 | }); 349 | 350 | it('throws if no private key is found', () => { 351 | delete (keyPair as any).__D; 352 | 353 | assert.throws(() => { 354 | keyPair.sign(hash); 355 | }, /Missing private key/); 356 | }); 357 | }); 358 | 359 | describe('schnorr signing', () => { 360 | it('creates signature', () => { 361 | const kP = ECPair.fromPrivateKey(ONE, { 362 | compressed: false, 363 | }); 364 | const h = Buffer.alloc(32, 2); 365 | const schnorrsig = Buffer.from( 366 | 'cde43b67d4326fa6ff1b40711615b692a997e193cc512f3a40e5cd4a5c9be18ca871296fa967f4dc13634c70d965223d637546a0b519050bae82c76d3ae627ff', 367 | 'hex', 368 | ); 369 | 370 | assert.deepStrictEqual( 371 | tools.toHex(kP.signSchnorr(h)), 372 | schnorrsig.toString('hex'), 373 | ); 374 | }); 375 | 376 | it('wraps tinysecp.signSchnorr', () => { 377 | assert.deepStrictEqual(keyPair.signSchnorr(hash), signature); 378 | }); 379 | 380 | it('throws if no private key is found', () => { 381 | delete (keyPair as any).__D; 382 | 383 | assert.throws(() => { 384 | keyPair.signSchnorr(hash); 385 | }, /Missing private key/); 386 | }); 387 | 388 | it('throws if signSchnorr() not found', () => { 389 | assert.throws(() => { 390 | keyPair = ECPairFactory({ 391 | ...tinysecp, 392 | signSchnorr: null, 393 | } as unknown as TinySecp256k1Interface).makeRandom(); 394 | keyPair.signSchnorr(hash); 395 | }, /signSchnorr not supported by ecc library/); 396 | }); 397 | }); 398 | 399 | describe('verify', () => { 400 | it('wraps tinysecp.verify', () => { 401 | assert.strictEqual(keyPair.verify(hash, signature), true); 402 | }); 403 | }); 404 | 405 | describe('schnorr verify', () => { 406 | it('checks signature', () => { 407 | const kP = ECPair.fromPrivateKey(ONE, { 408 | compressed: false, 409 | }); 410 | const h = Buffer.alloc(32, 2); 411 | const schnorrsig = Buffer.from( 412 | '4bc68cbd7c0b769b2dff262e9971756da7ab78402ed6f710c3788ce815e9c06a011bab7a527e33c6a1df0dad5ed05a04b8f3be656d8578502fef07f8215d37db', 413 | 'hex', 414 | ); 415 | 416 | assert.strictEqual(kP.verifySchnorr(h, schnorrsig), true); 417 | }); 418 | 419 | it('wraps tinysecp.verifySchnorr', () => { 420 | assert.strictEqual(keyPair.verifySchnorr(hash, signature), true); 421 | }); 422 | 423 | it('throws if verifySchnorr() not found', () => { 424 | assert.throws(() => { 425 | keyPair = ECPairFactory({ 426 | ...tinysecp, 427 | verifySchnorr: null, 428 | } as unknown as TinySecp256k1Interface).makeRandom(); 429 | keyPair.verifySchnorr(hash, signature); 430 | }, /verifySchnorr not supported by ecc library/); 431 | }); 432 | }); 433 | }); 434 | 435 | describe('optional low R signing', () => { 436 | const sig = Buffer.from( 437 | '95a6619140fca3366f1d3b013b0367c4f86e39508a50fdce' + 438 | 'e5245fbb8bd60aa6086449e28cf15387cf9f85100bfd0838624ca96759e59f65c10a00' + 439 | '16b86f5229', 440 | 'hex', 441 | ); 442 | const sigLowR = Buffer.from( 443 | '6a2660c226e8055afad317eeba918a304be79208d505' + 444 | '3bc5ea4a5e4c5892b4a061c717c5284ae5202d721c0e49b4717b79966280906b1d3b52' + 445 | '95d1fdde963c35', 446 | 'hex', 447 | ); 448 | const lowRKeyPair = ECPair.fromWIF( 449 | 'L3nThUzbAwpUiBAjR5zCu66ybXSPMr2zZ3ikp' + 'ScpTPiYTxBynfZu', 450 | ); 451 | const dataToSign = Buffer.from( 452 | 'b6c5c548a7f6164c8aa7af5350901626ebd69f9ae' + '2c1ecf8871f5088ec204cfe', 453 | 'hex', 454 | ); 455 | 456 | it('signs with normal R by default', () => { 457 | const signed = lowRKeyPair.sign(dataToSign); 458 | assert.deepStrictEqual(sig, Buffer.from(signed)); 459 | }); 460 | 461 | it('signs with low R when true is passed', () => { 462 | const signed = lowRKeyPair.sign(dataToSign, true); 463 | assert.deepStrictEqual(sigLowR, Buffer.from(signed)); 464 | }); 465 | }); 466 | }); 467 | --------------------------------------------------------------------------------