nostr-tools 0.24.1 → 1.0.0-alpha2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.json CHANGED
@@ -1,5 +1,9 @@
1
1
  {
2
2
  "root": true,
3
+
4
+ "parser": "@typescript-eslint/parser",
5
+ "plugins": ["@typescript-eslint"],
6
+
3
7
  "parserOptions": {
4
8
  "ecmaVersion": 9,
5
9
  "ecmaFeatures": {
@@ -14,9 +18,7 @@
14
18
  "node": true
15
19
  },
16
20
 
17
- "plugins": [
18
- "babel"
19
- ],
21
+ "plugins": ["babel"],
20
22
 
21
23
  "globals": {
22
24
  "document": false,
@@ -33,23 +35,23 @@
33
35
 
34
36
  "rules": {
35
37
  "accessor-pairs": 2,
36
- "arrow-spacing": [2, { "before": true, "after": true }],
38
+ "arrow-spacing": [2, {"before": true, "after": true}],
37
39
  "block-spacing": [2, "always"],
38
- "brace-style": [2, "1tbs", { "allowSingleLine": true }],
40
+ "brace-style": [2, "1tbs", {"allowSingleLine": true}],
39
41
  "comma-dangle": 0,
40
- "comma-spacing": [2, { "before": false, "after": true }],
42
+ "comma-spacing": [2, {"before": false, "after": true}],
41
43
  "comma-style": [2, "last"],
42
44
  "constructor-super": 2,
43
45
  "curly": [0, "multi-line"],
44
46
  "dot-location": [2, "property"],
45
47
  "eol-last": 2,
46
48
  "eqeqeq": [2, "allow-null"],
47
- "generator-star-spacing": [2, { "before": true, "after": true }],
48
- "handle-callback-err": [2, "^(err|error)$" ],
49
+ "generator-star-spacing": [2, {"before": true, "after": true}],
50
+ "handle-callback-err": [2, "^(err|error)$"],
49
51
  "indent": 0,
50
52
  "jsx-quotes": [2, "prefer-double"],
51
- "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
52
- "keyword-spacing": [2, { "before": true, "after": true }],
53
+ "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
54
+ "keyword-spacing": [2, {"before": true, "after": true}],
53
55
  "new-cap": 0,
54
56
  "new-parens": 0,
55
57
  "no-array-constructor": 2,
@@ -81,12 +83,12 @@
81
83
  "no-irregular-whitespace": 2,
82
84
  "no-iterator": 2,
83
85
  "no-label-var": 2,
84
- "no-labels": [2, { "allowLoop": false, "allowSwitch": false }],
86
+ "no-labels": [2, {"allowLoop": false, "allowSwitch": false}],
85
87
  "no-lone-blocks": 2,
86
88
  "no-mixed-spaces-and-tabs": 2,
87
89
  "no-multi-spaces": 2,
88
90
  "no-multi-str": 2,
89
- "no-multiple-empty-lines": [2, { "max": 2 }],
91
+ "no-multiple-empty-lines": [2, {"max": 2}],
90
92
  "no-native-reassign": 2,
91
93
  "no-negated-in-lhs": 2,
92
94
  "no-new": 0,
@@ -115,23 +117,34 @@
115
117
  "no-undef": 2,
116
118
  "no-undef-init": 2,
117
119
  "no-unexpected-multiline": 2,
118
- "no-unneeded-ternary": [2, { "defaultAssignment": false }],
120
+ "no-unneeded-ternary": [2, {"defaultAssignment": false}],
119
121
  "no-unreachable": 2,
120
- "no-unused-vars": [2, { "vars": "local", "args": "none", "varsIgnorePattern": "^_"}],
122
+ "no-unused-vars": [
123
+ 2,
124
+ {"vars": "local", "args": "none", "varsIgnorePattern": "^_"}
125
+ ],
121
126
  "no-useless-call": 2,
122
127
  "no-useless-constructor": 2,
123
128
  "no-with": 2,
124
- "one-var": [0, { "initialized": "never" }],
125
- "operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
129
+ "one-var": [0, {"initialized": "never"}],
130
+ "operator-linebreak": [
131
+ 2,
132
+ "after",
133
+ {"overrides": {"?": "before", ":": "before"}}
134
+ ],
126
135
  "padded-blocks": [2, "never"],
127
- "quotes": [2, "single", { "avoidEscape": true, "allowTemplateLiterals": true }],
136
+ "quotes": [
137
+ 2,
138
+ "single",
139
+ {"avoidEscape": true, "allowTemplateLiterals": true}
140
+ ],
128
141
  "semi": [2, "never"],
129
- "semi-spacing": [2, { "before": false, "after": true }],
142
+ "semi-spacing": [2, {"before": false, "after": true}],
130
143
  "space-before-blocks": [2, "always"],
131
144
  "space-before-function-paren": 0,
132
145
  "space-in-parens": [2, "never"],
133
146
  "space-infix-ops": 2,
134
- "space-unary-ops": [2, { "words": true, "nonwords": false }],
147
+ "space-unary-ops": [2, {"words": true, "nonwords": false}],
135
148
  "spaced-comment": 0,
136
149
  "template-curly-spacing": [2, "never"],
137
150
  "use-isnan": 2,
@@ -0,0 +1,19 @@
1
+ name: publish npm package
2
+
3
+ on:
4
+ push:
5
+ tags: [v*]
6
+
7
+ jobs:
8
+ publish-npm:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v3
12
+ - uses: actions/setup-node@v3
13
+ with:
14
+ node-version: 18
15
+ - run: yarn --ignore-engines
16
+ - run: node build.js
17
+ - run: npm publish
18
+ env:
19
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
@@ -0,0 +1,17 @@
1
+ name: test every commit
2
+ on:
3
+ push:
4
+ branches:
5
+ - master
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+ - uses: actions/setup-node@v3
13
+ with:
14
+ node-version: 18
15
+ - run: yarn --ignore-engines
16
+ - run: node build.js
17
+ - run: yarn test
package/README.md CHANGED
@@ -4,89 +4,160 @@ Tools for developing [Nostr](https://github.com/fiatjaf/nostr) clients.
4
4
 
5
5
  ## Usage
6
6
 
7
- ```js
8
- import {relayPool} from 'nostr-tools'
7
+ ### Generating a private key and a public key
9
8
 
10
- const pool = relayPool()
9
+ ```js
10
+ import { generatePrivateKey, getPublicKey } from 'nostr-tools'
11
11
 
12
- pool.setPrivateKey('<hex>') // optional
12
+ let sk = generatePrivateKey() # `sk` is a hex string
13
+ let pk = getPublicKey(sk) # `pk` is a hex string
14
+ ```
13
15
 
14
- pool.addRelay('ws://some.relay.com', {read: true, write: true})
15
- pool.addRelay('ws://other.relay.cool', {read: true, write: true})
16
+ ### Creating, signing and verifying events
16
17
 
17
- // example callback function for a subscription
18
- function onEvent(event, relay) {
19
- console.log(`got an event from ${relay.url} which is already validated.`, event)
18
+ ```js
19
+ import {
20
+ validateEvent,
21
+ verifySignature,
22
+ signEvent,
23
+ getEventHash,
24
+ getPublicKey
25
+ } from 'nostr-tools'
26
+
27
+ let event = {
28
+ kind: 1,
29
+ created_at: Math.floor(Date.now() / 1000),
30
+ tags: [],
31
+ content: 'hello'
20
32
  }
21
33
 
22
- // subscribing to a single user
23
- // author is the user's public key
24
- pool.sub({cb: onEvent, filter: {author: '<hex>'}})
25
-
26
- // or bulk follow
27
- pool.sub({cb:(event, relay) => {...}, filter: {authors: ['<hex1>', '<hex2>', ..., '<hexn>']}})
28
-
29
- // reuse a subscription channel
30
- const mySubscription = pool.sub({cb: ..., filter: ....})
31
- mySubscription.sub({filter: ....})
32
- mySubscription.sub({cb: ...})
33
- mySubscription.unsub()
34
-
35
- // get specific event
36
- const specificChannel = pool.sub({
37
- cb: (event, relay) => {
38
- console.log('got specific event from relay', event, relay)
39
- specificChannel.unsub()
40
- },
41
- filter: {id: '<hex>'}
42
- })
34
+ event.id = getEventHash(event.id)
35
+ event.pubkey = getPublicKey(privateKey)
36
+ event.sig = await signEvent(event, privateKey)
43
37
 
44
- // or get a specific event plus all the events that reference it in the 'e' tag
45
- pool.sub({ cb: (event, relay) => { ... }, filter: [{id: '<hex>'}, {'#e': '<hex>'}] })
38
+ let ok = validateEvent(event)
39
+ let veryOk = await verifySignature(event)
40
+ ```
46
41
 
47
- // get all events
48
- pool.sub({cb: (event, relay) => {...}, filter: {}})
42
+ ### Interacting with a relay
49
43
 
50
- // get recent events
51
- pool.sub({cb: (event, relay) => {...}, filter: {since: timestamp}})
44
+ ```js
45
+ import {
46
+ relayInit,
47
+ generatePrivateKey,
48
+ getPublicKey,
49
+ getEventHash,
50
+ signEvent
51
+ } from 'nostr-tools'
52
+
53
+ const relay = relayInit('wss://relay.example.com')
54
+ relay.connect()
55
+
56
+ relay.on('connect', () => {
57
+ console.log(`connected to ${relay.url}`)
58
+ })
59
+ relay.on('error', () => {
60
+ console.log(`failed to connect to ${relay.url}`)
61
+ })
52
62
 
53
- // publishing events(inside an async function):
54
- const ev = await pool.publish(eventObject, (status, url) => {
55
- if (status === 0) {
56
- console.log(`publish request sent to ${url}`)
63
+ // let's query for an event that exists
64
+ let sub = relay.sub([
65
+ {
66
+ ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027']
57
67
  }
58
- if (status === 1) {
59
- console.log(`event published by ${url}`, ev)
68
+ ])
69
+ sub.on('event', event => {
70
+ console.log('we got the event we wanted:', event)
71
+ })
72
+ sub.on('eose', () => {
73
+ sub.unsub()
74
+ })
75
+
76
+ // let's publish a new event while simultaneously monitoring the relay for it
77
+ let sk = generatePrivateKey()
78
+ let pk = getPublicKey(sk)
79
+
80
+ let sub = relay.sub([
81
+ {
82
+ kinds: [1],
83
+ authors: [pk]
60
84
  }
85
+ ])
86
+
87
+ sub.on('event', event => {
88
+ console.log('got event:', event)
89
+ })
90
+
91
+ let event = {
92
+ kind: 1,
93
+ pubkey: pk,
94
+ created_at: Math.floor(Date.now() / 1000),
95
+ tags: [],
96
+ content: 'hello world'
97
+ }
98
+ event.id = getEventHash(event)
99
+ event.sig = await signEvent(event, sk)
100
+
101
+ let pub = relay.publish(event)
102
+ pub.on('ok', () => {
103
+ console.log(`{relay.url} has accepted our event`)
104
+ })
105
+ pub.on('seen', () => {
106
+ console.log(`we saw the event on {relay.url}`)
107
+ })
108
+ pub.on('failed', reason => {
109
+ console.log(`failed to publish to {relay.url}: ${reason}`)
61
110
  })
62
- // it will be signed automatically with the key supplied above
63
- // or pass an already signed event to bypass this
64
111
 
65
- // subscribing to a new relay
66
- pool.addRelay('<url>')
67
- // will automatically subscribe to the all the events called with .sub above
112
+ await relay.close()
68
113
  ```
69
114
 
70
- All functions expect bytearrays as hex strings and output bytearrays as hex strings.
115
+ ### Encrypting and decrypting direct messages
71
116
 
72
- For other utils please read the source (for now).
117
+ ```js
118
+ import {nip04, getPublicKey, generatePrivateKey} from 'nostr-tools'
119
+
120
+ // sender
121
+ let sk1 = generatePrivateKey()
122
+ let pk1 = getPublicKey(sk1)
123
+
124
+ // receiver
125
+ let sk2 = generatePrivateKey()
126
+ let pk2 = getPublicKey(sk2)
127
+
128
+ // on the sender side
129
+ let message = 'hello'
130
+ let ciphertext = nip04.encrypt(sk1, pk2, 'hello')
131
+
132
+ let event = {
133
+ kind: 4,
134
+ pubkey: pk1,
135
+ tags: [['p', pk2]],
136
+ content: ciphertext,
137
+ ...otherProperties
138
+ }
73
139
 
74
- ### Using from the browser (if you don't want to use a bundler)
140
+ sendEvent(event)
75
141
 
76
- You can import nostr-tools as an ES module. Just add a script tag like this:
142
+ // on the receiver side
77
143
 
78
- ```html
79
- <script type="module">
80
- import {generatePrivateKey} from 'https://unpkg.com/nostr-tools/nostr.js'
81
- console.log(generatePrivateKey())
82
- </script>
144
+ sub.on('event', (event) => {
145
+ let sender = event.tags.find(([k, v]) => k === 'p' && && v && v !== '')[1]
146
+ pk1 === sender
147
+ let plaintext = nip04.decrypt(sk2, pk1, event.content)
148
+ })
83
149
  ```
84
150
 
85
- And import whatever function you would import from `"nostr-tools"` in a bundler.
151
+ Please consult the tests or [the source code](https://github.com/fiatjaf/nostr-tools) for more information that isn't available here.
86
152
 
87
- ## TypeScript
153
+ ### Using from the browser (if you don't want to use a bundler)
88
154
 
89
- This module has hand-authored TypeScript declarations. `npm run check-ts` will run a lint-check script to ensure the typings can be loaded and call at least a few standard library functions. It's not at all comprehensive and likely to contain bugs. Issues welcome; tag @rcoder as needed.
155
+ ```html
156
+ <script src="https://unpkg.com/nostr-tools/nostr.bundle.js"></script>
157
+ <script>
158
+ window.NostrTools.generatePrivateKey('...') // and so on
159
+ </script>
160
+ ```
90
161
 
91
162
  ## License
92
163
 
package/build.js ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+
3
+ const esbuild = require('esbuild')
4
+ const alias = require('esbuild-plugin-alias')
5
+
6
+ let common = {
7
+ entryPoints: ['index.ts'],
8
+ bundle: true,
9
+ plugins: [
10
+ alias({
11
+ stream: require.resolve('readable-stream')
12
+ })
13
+ ],
14
+ sourcemap: 'external'
15
+ }
16
+
17
+ esbuild
18
+ .build({
19
+ ...common,
20
+ outfile: 'lib/nostr.esm.js',
21
+ format: 'esm',
22
+ packages: 'external'
23
+ })
24
+ .then(() => console.log('esm build success.'))
25
+
26
+ esbuild
27
+ .build({
28
+ ...common,
29
+ outfile: 'lib/nostr.cjs.js',
30
+ format: 'cjs',
31
+ packages: 'external'
32
+ })
33
+ .then(() => console.log('cjs build success.'))
34
+
35
+ esbuild
36
+ .build({
37
+ ...common,
38
+ outfile: 'lib/nostr.bundle.js',
39
+ format: 'iife',
40
+ globalName: 'NostrTools',
41
+ define: {
42
+ window: 'self',
43
+ global: 'self',
44
+ process: '{"env": {}}'
45
+ }
46
+ })
47
+ .then(() => console.log('standalone build success.'))
package/event.test.js ADDED
@@ -0,0 +1,49 @@
1
+ /* eslint-env jest */
2
+
3
+ const {
4
+ validateEvent,
5
+ verifySignature,
6
+ signEvent,
7
+ getEventHash,
8
+ getPublicKey
9
+ } = require('./lib/nostr.cjs')
10
+
11
+ const event = {
12
+ id: 'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027',
13
+ kind: 1,
14
+ pubkey: '22a12a128a3be27cd7fb250cbe796e692896398dc1440ae3fa567812c8107c1c',
15
+ created_at: 1670869179,
16
+ content:
17
+ 'NOSTR "WINE-ACCOUNT" WITH HARVEST DATE STAMPED\n\n\n"The older the wine, the greater its reputation"\n\n\n22a12a128a3be27cd7fb250cbe796e692896398dc1440ae3fa567812c8107c1c\n\n\nNWA 2022-12-12\nAA',
18
+ tags: [['client', 'astral']],
19
+ sig: 'f110e4fdf67835fb07abc72469933c40bdc7334615610cade9554bf00945a1cebf84f8d079ec325d26fefd76fe51cb589bdbe208ac9cdbd63351ddad24a57559'
20
+ }
21
+
22
+ const unsigned = {
23
+ created_at: 1671217411,
24
+ kind: 0,
25
+ tags: [],
26
+ content:
27
+ '{"name":"fiatjaf","about":"buy my merch at fiatjaf store","picture":"https://fiatjaf.com/static/favicon.jpg","nip05":"_@fiatjaf.com"}'
28
+ }
29
+
30
+ const privateKey =
31
+ '5c6c25b7ef18d8633e97512159954e1aa22809c6b763e94b9f91071836d00217'
32
+
33
+ test('validate event', () => {
34
+ expect(validateEvent(event)).toBeTruthy()
35
+ })
36
+
37
+ test('check signature', async () => {
38
+ expect(await verifySignature(event)).toBeTruthy()
39
+ })
40
+
41
+ test('sign event', async () => {
42
+ let sig = await signEvent(unsigned, privateKey)
43
+ let hash = getEventHash(unsigned)
44
+ let pubkey = getPublicKey(privateKey)
45
+
46
+ let signed = {...unsigned, id: hash, sig, pubkey}
47
+
48
+ expect(await verifySignature(signed)).toBeTruthy()
49
+ })
@@ -1,18 +1,29 @@
1
1
  import {Buffer} from 'buffer'
2
- import createHash from 'create-hash'
2
+ // @ts-ignore
3
3
  import * as secp256k1 from '@noble/secp256k1'
4
+ import {sha256} from '@noble/hashes/sha256'
4
5
 
5
- export function getBlankEvent() {
6
+ export type Event = {
7
+ id?: string
8
+ sig?: string
9
+ kind: number
10
+ tags: string[][]
11
+ pubkey: string
12
+ content: string
13
+ created_at: number
14
+ }
15
+
16
+ export function getBlankEvent(): Event {
6
17
  return {
7
18
  kind: 255,
8
- pubkey: null,
19
+ pubkey: '',
9
20
  content: '',
10
21
  tags: [],
11
22
  created_at: 0
12
23
  }
13
24
  }
14
25
 
15
- export function serializeEvent(evt) {
26
+ export function serializeEvent(evt: Event): string {
16
27
  return JSON.stringify([
17
28
  0,
18
29
  evt.pubkey,
@@ -23,14 +34,12 @@ export function serializeEvent(evt) {
23
34
  ])
24
35
  }
25
36
 
26
- export function getEventHash(event) {
27
- let eventHash = createHash('sha256')
28
- .update(Buffer.from(serializeEvent(event)))
29
- .digest()
37
+ export function getEventHash(event: Event): string {
38
+ let eventHash = sha256(Buffer.from(serializeEvent(event)))
30
39
  return Buffer.from(eventHash).toString('hex')
31
40
  }
32
41
 
33
- export function validateEvent(event) {
42
+ export function validateEvent(event: Event): boolean {
34
43
  if (event.id !== getEventHash(event)) return false
35
44
  if (typeof event.content !== 'string') return false
36
45
  if (typeof event.created_at !== 'number') return false
@@ -47,12 +56,14 @@ export function validateEvent(event) {
47
56
  return true
48
57
  }
49
58
 
50
- export function verifySignature(event) {
59
+ export function verifySignature(
60
+ event: Event & {id: string; sig: string}
61
+ ): Promise<boolean> {
51
62
  return secp256k1.schnorr.verify(event.sig, event.id, event.pubkey)
52
63
  }
53
64
 
54
- export async function signEvent(event, key) {
65
+ export async function signEvent(event: Event, key: string): Promise<string> {
55
66
  return Buffer.from(
56
- await secp256k1.schnorr.sign(getEventHash(event), key)
67
+ await secp256k1.schnorr.sign(event.id || getEventHash(event), key)
57
68
  ).toString('hex')
58
69
  }
package/filter.test.js ADDED
@@ -0,0 +1,42 @@
1
+ /* eslint-env jest */
2
+
3
+ const {matchFilters} = require('./lib/nostr.cjs')
4
+
5
+ test('test if filters match', () => {
6
+ ;[
7
+ {
8
+ filters: [{ids: ['i']}],
9
+ good: [{id: 'i'}],
10
+ bad: [{id: 'j'}]
11
+ },
12
+ {
13
+ filters: [{authors: ['abc']}, {kinds: [1, 3]}],
14
+ good: [
15
+ {pubkey: 'xyz', kind: 3},
16
+ {pubkey: 'abc', kind: 12},
17
+ {pubkey: 'abc', kind: 1}
18
+ ],
19
+ bad: [{pubkey: 'hhh', kind: 12}]
20
+ },
21
+ {
22
+ filters: [{'#e': ['yyy'], since: 444}],
23
+ good: [
24
+ {
25
+ tags: [
26
+ ['e', 'uuu'],
27
+ ['e', 'yyy']
28
+ ],
29
+ created_at: 555
30
+ }
31
+ ],
32
+ bad: [{tags: [['e', 'uuu']], created_at: 111}]
33
+ }
34
+ ].forEach(({filters, good, bad}) => {
35
+ good.forEach(ev => {
36
+ expect(matchFilters(filters, ev)).toBeTruthy()
37
+ })
38
+ bad.forEach(ev => {
39
+ expect(matchFilters(filters, ev)).toBeFalsy()
40
+ })
41
+ })
42
+ })
@@ -1,4 +1,18 @@
1
- export function matchFilter(filter, event) {
1
+ import {Event} from './event'
2
+
3
+ export type Filter = {
4
+ ids?: string[]
5
+ kinds?: number[]
6
+ authors?: string[]
7
+ since?: number
8
+ until?: number
9
+ [key: `#${string}`]: string[]
10
+ }
11
+
12
+ export function matchFilter(
13
+ filter: Filter,
14
+ event: Event & {id: string}
15
+ ): boolean {
2
16
  if (filter.ids && filter.ids.indexOf(event.id) === -1) return false
3
17
  if (filter.kinds && filter.kinds.indexOf(event.kind) === -1) return false
4
18
  if (filter.authors && filter.authors.indexOf(event.pubkey) === -1)
@@ -6,10 +20,12 @@ export function matchFilter(filter, event) {
6
20
 
7
21
  for (let f in filter) {
8
22
  if (f[0] === '#') {
23
+ let tagName = f.slice(1)
24
+ let values = filter[`#${tagName}`]
9
25
  if (
10
- filter[f] &&
26
+ values &&
11
27
  !event.tags.find(
12
- ([t, v]) => t === f.slice(1) && filter[f].indexOf(v) !== -1
28
+ ([t, v]) => t === f.slice(1) && values.indexOf(v) !== -1
13
29
  )
14
30
  )
15
31
  return false
@@ -22,7 +38,10 @@ export function matchFilter(filter, event) {
22
38
  return true
23
39
  }
24
40
 
25
- export function matchFilters(filters, event) {
41
+ export function matchFilters(
42
+ filters: Filter[],
43
+ event: Event & {id: string}
44
+ ): boolean {
26
45
  for (let i = 0; i < filters.length; i++) {
27
46
  if (matchFilter(filters[i], event)) return true
28
47
  }
package/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from './keys'
2
+ export * from './relay'
3
+ export * from './event'
4
+ export * from './filter'
5
+
6
+ export * as nip04 from './nip04'
7
+ export * as nip05 from './nip05'
8
+ export * as nip06 from './nip06'
package/keys.test.js ADDED
@@ -0,0 +1,20 @@
1
+ /* eslint-env jest */
2
+
3
+ const {generatePrivateKey, getPublicKey} = require('./lib/nostr.cjs')
4
+
5
+ test('test private key generation', () => {
6
+ expect(generatePrivateKey()).toMatch(/[a-f0-9]{64}/)
7
+ })
8
+
9
+ test('test public key generation', () => {
10
+ expect(getPublicKey(generatePrivateKey())).toMatch(/[a-f0-9]{64}/)
11
+ })
12
+
13
+ test('test public key from private key deterministic', () => {
14
+ let sk = generatePrivateKey()
15
+ let pk = getPublicKey(sk)
16
+
17
+ for (let i = 0; i < 5; i++) {
18
+ expect(getPublicKey(sk)).toEqual(pk)
19
+ }
20
+ })
@@ -1,10 +1,10 @@
1
1
  import * as secp256k1 from '@noble/secp256k1'
2
2
  import {Buffer} from 'buffer'
3
3
 
4
- export function generatePrivateKey() {
4
+ export function generatePrivateKey(): string {
5
5
  return Buffer.from(secp256k1.utils.randomPrivateKey()).toString('hex')
6
6
  }
7
7
 
8
- export function getPublicKey(privateKey) {
8
+ export function getPublicKey(privateKey: string): string {
9
9
  return Buffer.from(secp256k1.schnorr.getPublicKey(privateKey)).toString('hex')
10
10
  }