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.
- package/README.md +52 -0
- package/THIRD_PARTY_NOTICES.md +46 -0
- package/event/helpers/core.js +120 -0
- package/event/helpers/serialize.js +56 -0
- package/event/index.js +81 -74
- package/event/js/index.js +13 -0
- package/helpers/secp256k1-wasm/backend.js +99 -0
- package/helpers/secp256k1-wasm/binding.js +229 -0
- package/helpers/secp256k1-wasm/generated.js +25 -0
- package/key/helpers/core.js +149 -0
- package/key/index.js +57 -123
- package/key/js/index.js +13 -0
- package/kind/index.js +2 -0
- package/package.json +7 -1
|
@@ -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
|
+
}
|