libp2r2p 0.11.35 → 0.12.0

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.
@@ -0,0 +1,229 @@
1
+ import { serializeEventChunks } from '../../event/helpers/serialize.js'
2
+ import { wasmBase64, wasmExports, wasmImports } from './generated.js'
3
+
4
+ const MEMORY_BYTES = 256 * 1024
5
+ const INPUT_BYTES = 16 * 1024
6
+ const PRIVATE_KEY_BYTES = 32
7
+ const KEYPAIR_BYTES = 96
8
+ const XONLY_KEY_BYTES = 64
9
+ const SIGNATURE_BYTES = 64
10
+ const PUBLIC_KEY_BYTES = 32
11
+ const HASH_CONTEXT_BYTES = 4
12
+ const SHA256_STATE_BYTES = 104
13
+ const CONTEXT_SIGN = 1 | (1 << 9)
14
+ const CONTEXT_VERIFY = 1 | (1 << 8)
15
+ const textEncoder = new TextEncoder()
16
+
17
+ function decodeBase64 (value) {
18
+ if (typeof atob !== 'function') throw Error('Base64 decoding is unavailable')
19
+ const binary = atob(value)
20
+ const bytes = new Uint8Array(binary.length)
21
+ for (let index = 0; index < binary.length; index++) {
22
+ bytes[index] = binary.charCodeAt(index)
23
+ }
24
+ return bytes
25
+ }
26
+
27
+ function requireBytes (value, length, label) {
28
+ if (!(value instanceof Uint8Array) || value.length !== length) {
29
+ throw TypeError(`${label} must be a ${length}-byte Uint8Array`)
30
+ }
31
+ }
32
+
33
+ export async function createSecp256k1WasmBackend () {
34
+ if (typeof WebAssembly !== 'object') throw Error('WebAssembly is unavailable')
35
+
36
+ const heap = { bytes: null, words: null }
37
+ const importObject = {
38
+ a: {
39
+ [wasmImports.abort] () {
40
+ throw Error('secp256k1 WASM aborted')
41
+ },
42
+ [wasmImports.resize] (size) {
43
+ throw Error(`secp256k1 WASM requested heap growth to ${size} bytes`)
44
+ },
45
+ [wasmImports.fd_write] (_fd, vectors, count, written) {
46
+ let total = 0
47
+ for (let index = 0; index < count; index++) {
48
+ total += heap.words[(vectors + 4) >> 2]
49
+ vectors += 8
50
+ }
51
+ heap.words[written >> 2] = total
52
+ return 0
53
+ }
54
+ }
55
+ }
56
+ const { instance } = await WebAssembly.instantiate(decodeBase64(wasmBase64), importObject)
57
+ const api = {}
58
+ for (const [name, symbol] of Object.entries(wasmExports)) {
59
+ api[name] = instance.exports[symbol]
60
+ }
61
+ heap.bytes = new Uint8Array(api.memory.buffer)
62
+ heap.words = new Uint32Array(api.memory.buffer)
63
+ const bytes = heap.bytes
64
+ if (bytes.length !== MEMORY_BYTES) {
65
+ throw Error(`Unexpected secp256k1 WASM memory size: ${bytes.length}`)
66
+ }
67
+ api.init()
68
+
69
+ const allocate = size => {
70
+ const pointer = api.malloc(size)
71
+ if (!pointer) throw Error(`Failed to allocate ${size} WASM bytes`)
72
+ return pointer
73
+ }
74
+ const context = api.context_create(CONTEXT_SIGN | CONTEXT_VERIFY)
75
+ if (!context) throw Error('Failed to create secp256k1 context')
76
+ const secretKey = allocate(PRIVATE_KEY_BYTES)
77
+ const entropy = allocate(PRIVATE_KEY_BYTES)
78
+ const messageHash = allocate(PRIVATE_KEY_BYTES)
79
+ const publicKey = allocate(PUBLIC_KEY_BYTES)
80
+ const signature = allocate(SIGNATURE_BYTES)
81
+ const keypair = allocate(KEYPAIR_BYTES)
82
+ const xonlyPublicKey = allocate(XONLY_KEY_BYTES)
83
+ const hashContext = allocate(HASH_CONTEXT_BYTES)
84
+ const sha256State = allocate(SHA256_STATE_BYTES)
85
+ const input = allocate(INPUT_BYTES)
86
+ api.hash_ctx_init(hashContext)
87
+
88
+ const clearKeyMaterial = () => {
89
+ bytes.fill(0, secretKey, secretKey + PRIVATE_KEY_BYTES)
90
+ bytes.fill(0, entropy, entropy + PRIVATE_KEY_BYTES)
91
+ bytes.fill(0, keypair, keypair + KEYPAIR_BYTES)
92
+ }
93
+
94
+ const withKeypair = (secret, callback) => {
95
+ requireBytes(secret, PRIVATE_KEY_BYTES, 'Secret key')
96
+ try {
97
+ bytes.set(secret, secretKey)
98
+ if (api.keypair_create(context, keypair, secretKey) !== 1) {
99
+ throw Error('Invalid secp256k1 secret key')
100
+ }
101
+ return callback()
102
+ } finally {
103
+ clearKeyMaterial()
104
+ }
105
+ }
106
+
107
+ const derivePublicKey = () => {
108
+ if (api.keypair_xonly_pub(context, xonlyPublicKey, null, keypair) !== 1) {
109
+ throw Error('Failed to derive secp256k1 public key')
110
+ }
111
+ if (api.xonly_pubkey_serialize(context, publicKey, xonlyPublicKey) !== 1) {
112
+ throw Error('Failed to serialize secp256k1 public key')
113
+ }
114
+ return bytes.slice(publicKey, publicKey + PUBLIC_KEY_BYTES)
115
+ }
116
+
117
+ const writeBytesToHash = data => {
118
+ for (let offset = 0; offset < data.length; offset += INPUT_BYTES) {
119
+ const chunk = data.subarray(offset, Math.min(offset + INPUT_BYTES, data.length))
120
+ bytes.set(chunk, input)
121
+ api.sha256_write(hashContext, sha256State, input, chunk.length)
122
+ }
123
+ }
124
+
125
+ const writeTextToHash = text => {
126
+ if (typeof textEncoder.encodeInto !== 'function') {
127
+ writeBytesToHash(textEncoder.encode(text))
128
+ return
129
+ }
130
+
131
+ let offset = 0
132
+ while (offset < text.length) {
133
+ const target = bytes.subarray(input, input + INPUT_BYTES)
134
+ const { read, written } = textEncoder.encodeInto(text.slice(offset), target)
135
+ if (read === 0 && written === 0) throw Error('UTF-8 encoding stalled')
136
+ api.sha256_write(hashContext, sha256State, input, written)
137
+ offset += read
138
+ }
139
+ }
140
+
141
+ const dispose = () => {
142
+ clearKeyMaterial()
143
+ }
144
+
145
+ return {
146
+ memoryBytes: bytes.length,
147
+
148
+ getPublicKey (secret) {
149
+ return withKeypair(secret, derivePublicKey)
150
+ },
151
+
152
+ hashEvent (event) {
153
+ api.sha256_initialize(sha256State)
154
+ for (const chunk of serializeEventChunks(event, { validate: false })) {
155
+ writeTextToHash(chunk)
156
+ }
157
+ api.sha256_finalize(hashContext, sha256State, messageHash)
158
+ return bytes.slice(messageHash, messageHash + PRIVATE_KEY_BYTES)
159
+ },
160
+
161
+ signHash (hash, secret, auxiliaryRandom) {
162
+ requireBytes(hash, PRIVATE_KEY_BYTES, 'Message hash')
163
+ requireBytes(auxiliaryRandom, PRIVATE_KEY_BYTES, 'Auxiliary randomness')
164
+ return withKeypair(secret, () => {
165
+ bytes.set(hash, messageHash)
166
+ bytes.set(auxiliaryRandom, entropy)
167
+ if (
168
+ api.schnorrsig_sign32(
169
+ context,
170
+ signature,
171
+ messageHash,
172
+ keypair,
173
+ entropy
174
+ ) !== 1
175
+ ) {
176
+ throw Error('Failed to sign secp256k1 message')
177
+ }
178
+ return bytes.slice(signature, signature + SIGNATURE_BYTES)
179
+ })
180
+ },
181
+
182
+ signEventHash (secret, prepareHash, auxiliaryRandom) {
183
+ requireBytes(auxiliaryRandom, PRIVATE_KEY_BYTES, 'Auxiliary randomness')
184
+ return withKeypair(secret, () => {
185
+ const publicKeyBytes = derivePublicKey()
186
+ const hash = prepareHash(publicKeyBytes)
187
+ requireBytes(hash, PRIVATE_KEY_BYTES, 'Message hash')
188
+ bytes.set(hash, messageHash)
189
+ bytes.set(auxiliaryRandom, entropy)
190
+ if (
191
+ api.schnorrsig_sign32(
192
+ context,
193
+ signature,
194
+ messageHash,
195
+ keypair,
196
+ entropy
197
+ ) !== 1
198
+ ) {
199
+ throw Error('Failed to sign secp256k1 message')
200
+ }
201
+ return {
202
+ publicKey: publicKeyBytes,
203
+ signature: bytes.slice(signature, signature + SIGNATURE_BYTES)
204
+ }
205
+ })
206
+ },
207
+
208
+ verifyHash (signatureBytes, hash, publicKeyBytes) {
209
+ requireBytes(signatureBytes, SIGNATURE_BYTES, 'Signature')
210
+ requireBytes(hash, PRIVATE_KEY_BYTES, 'Message hash')
211
+ requireBytes(publicKeyBytes, PUBLIC_KEY_BYTES, 'Public key')
212
+ bytes.set(signatureBytes, signature)
213
+ bytes.set(hash, messageHash)
214
+ bytes.set(publicKeyBytes, publicKey)
215
+ if (api.xonly_pubkey_parse(context, xonlyPublicKey, publicKey) !== 1) {
216
+ return false
217
+ }
218
+ return api.schnorrsig_verify(
219
+ context,
220
+ signature,
221
+ messageHash,
222
+ PRIVATE_KEY_BYTES,
223
+ xonlyPublicKey
224
+ ) === 1
225
+ },
226
+
227
+ dispose
228
+ }
229
+ }