index.ts (8916B)
1 import { binary } from './supercop.wasm'; 2 3 const Module = (async () => { 4 const memory = new WebAssembly.Memory({initial: 4}); 5 const imports = {env: {memory}} as { env: { memory: WebAssembly.Memory, __heap_base?: WebAssembly.Global } }; 6 7 if ('function' === typeof WebAssembly.Global) { 8 imports.env.__heap_base = new WebAssembly.Global({value: 'i32', mutable: true}); 9 } 10 11 const program = await WebAssembly.instantiate(binary, imports); 12 13 return { 14 memory : memory, 15 instance: program.instance, 16 exports : program.instance.exports, 17 } as { 18 memory : WebAssembly.Memory; 19 instance: WebAssembly.Instance; 20 exports : {[index:string]:any}; 21 }; 22 })(); 23 24 25 function randomBytes(length: number) { 26 return Buffer.from(new Array(length).fill(0).map(()=>Math.floor(Math.random()*256))); 27 } 28 29 export function createSeed() { 30 return randomBytes(32); 31 } 32 33 export type PublicKey = Buffer; 34 export type SecretKey = Buffer; 35 export type Seed = Buffer; 36 export type Signature = Buffer; 37 38 function xIsBuffer(data: unknown): data is Buffer { 39 return Buffer.isBuffer(data); 40 } 41 42 export function isSeed(data: unknown): data is Seed { 43 if (!xIsBuffer(data)) return false; 44 return data.length === 32; 45 } 46 47 export function isPublicKey(data: unknown): data is PublicKey { 48 if (!xIsBuffer(data)) return false; 49 return data.length === 32; 50 } 51 52 export function isSignature(data: unknown): data is Signature { 53 if (!xIsBuffer(data)) return false; 54 return data.length === 64; 55 } 56 57 export function isSecretKey(data: unknown): data is SecretKey { 58 if (!xIsBuffer(data)) return false; 59 return data.length === 64; 60 } 61 62 export class KeyPair { 63 publicKey?: PublicKey; 64 secretKey?: SecretKey; 65 66 constructor() { 67 // Intentionally empty 68 } 69 70 // Passes signing on to the exported stand-alone method 71 // Async, so the error = promise rejection 72 async sign(message: string | Buffer) { 73 if (!isSecretKey(this.secretKey)) throw new Error('No secret key on this keypair, only verification is possible'); 74 if (!isPublicKey(this.publicKey)) throw new Error('Invalid public key'); 75 return sign(message, this.publicKey, this.secretKey); 76 } 77 78 // Passes verification on to the exported stand-alone method 79 verify(signature: number[] | Signature, message: string | Buffer) { 80 if (!isPublicKey(this.publicKey)) throw new Error('Invalid public key'); 81 return verify(signature, message, this.publicKey); 82 } 83 84 keyExchange(theirPublicKey?: number[] | PublicKey) { 85 if (!isSecretKey(this.secretKey)) throw new Error('Invalid secret key'); 86 return keyExchange(theirPublicKey, this.secretKey); 87 } 88 89 toJSON() { 90 return { 91 publicKey: this.publicKey ? [...this.publicKey] : undefined, 92 secretKey: this.secretKey ? [...this.secretKey] : undefined, 93 }; 94 } 95 96 static create(seed: number[] | Seed) { 97 return createKeyPair(seed); 98 } 99 100 static from( data: { publicKey: number[] | PublicKey, secretKey?: number[] | SecretKey } ) { 101 return keyPairFrom(data); 102 } 103 104 } 105 106 export function keyPairFrom( data: { publicKey: number[] | PublicKey, secretKey?: number[] | SecretKey } ): KeyPair { 107 if ('object' !== typeof data) throw new Error('Invalid input data'); 108 if (!data) throw new Error('Invalid input data'); 109 110 // Sanitization and sanity checking 111 data = { ...data }; 112 if (Array.isArray(data.publicKey)) data.publicKey = Buffer.from(data.publicKey); 113 if (Array.isArray(data.secretKey)) data.secretKey = Buffer.from(data.secretKey); 114 if (!isPublicKey(data.publicKey)) throw new Error('Invalid public key'); 115 // Not checking the secretKey, allowed to be missing 116 117 const keypair = new KeyPair(); 118 Object.assign(keypair, data); 119 return keypair; 120 } 121 122 export async function createKeyPair( seed: number[] | Seed ): Promise<KeyPair> { 123 124 // Pre-fetch module components 125 const fn = (await Module).exports; 126 const mem = (await Module).memory; 127 128 // Ensure we have a valid seed 129 if (Array.isArray(seed)) seed = Buffer.from(seed); 130 if (!isSeed(seed)) throw new Error('Invalid seed'); 131 132 // Reserve wasm-side memory 133 const seedPtr = fn._malloc(32); 134 const publicKeyPtr = fn._malloc(32); 135 const secretKeyPtr = fn._malloc(64); 136 137 const seedBuf = new Uint8Array(mem.buffer, seedPtr , 32); 138 const publicKey = new Uint8Array(mem.buffer, publicKeyPtr, 32); 139 const secretKey = new Uint8Array(mem.buffer, secretKeyPtr, 64); 140 141 seedBuf.set(seed); 142 143 fn.create_keypair(publicKeyPtr, secretKeyPtr, seedPtr); 144 145 fn._free(seedPtr); 146 fn._free(publicKeyPtr); 147 fn._free(secretKeyPtr); 148 149 return keyPairFrom({ 150 publicKey: Buffer.from(publicKey), 151 secretKey: Buffer.from(secretKey), 152 }); 153 } 154 155 export async function sign( 156 message: string | Buffer, 157 publicKey: number[] | PublicKey, 158 secretKey: number[] | SecretKey 159 ): Promise<Signature> { 160 161 // Pre-fetch module components 162 const fn = (await Module).exports; 163 const mem = (await Module).memory; 164 165 // Sanitization and sanity checking 166 if (Array.isArray(publicKey)) publicKey = Buffer.from(publicKey); 167 if (Array.isArray(secretKey)) secretKey = Buffer.from(secretKey); 168 if (!isPublicKey(publicKey)) throw new Error('Invalid public key'); 169 if (!isSecretKey(secretKey)) throw new Error('Invalid secret key'); 170 if ('string' === typeof message) message = Buffer.from(message); 171 172 // Allocate memory on the wasm side to transfer variables 173 const messageLen = message.length; 174 const messageArrPtr = fn._malloc(messageLen); 175 const messageArr = new Uint8Array(mem.buffer, messageArrPtr, messageLen); 176 const publicKeyArrPtr = fn._malloc(32); 177 const publicKeyArr = new Uint8Array(mem.buffer, publicKeyArrPtr, 32); 178 const secretKeyArrPtr = fn._malloc(64); 179 const secretKeyArr = new Uint8Array(mem.buffer, secretKeyArrPtr, 64); 180 const sigPtr = fn._malloc(64); 181 const sig = new Uint8Array(mem.buffer, sigPtr, 64); 182 183 messageArr.set(message); 184 publicKeyArr.set(publicKey); 185 secretKeyArr.set(secretKey); 186 187 await fn.sign(sigPtr, messageArrPtr, messageLen, publicKeyArrPtr, secretKeyArrPtr); 188 189 // Free used memory on wasm side 190 fn._free(messageArrPtr); 191 fn._free(publicKeyArrPtr); 192 fn._free(secretKeyArrPtr); 193 fn._free(sigPtr); 194 195 return Buffer.from(sig); 196 } 197 198 export async function verify( 199 signature: number[] | Signature, 200 message: string | Buffer, 201 publicKey: number[] | PublicKey 202 ): Promise<boolean> { 203 204 const fn = (await Module).exports; 205 const mem = (await Module).memory; 206 207 // Sanitization and sanity checking 208 if (Array.isArray(signature)) signature = Buffer.from(signature); 209 if (Array.isArray(publicKey)) publicKey = Buffer.from(publicKey); 210 if (!isPublicKey(publicKey)) throw new Error('Invalid public key'); 211 if (!isSignature(signature)) throw new Error('Invalid signature'); 212 if ('string' === typeof message) message = Buffer.from(message); 213 214 // Allocate memory on the wasm side to transfer variables 215 const messageLen = message.length; 216 const messageArrPtr = fn._malloc(messageLen); 217 const messageArr = new Uint8Array(mem.buffer, messageArrPtr, messageLen); 218 const signatureArrPtr = fn._malloc(64); 219 const signatureArr = new Uint8Array(mem.buffer, signatureArrPtr, 64); 220 const publicKeyArrPtr = fn._malloc(32); 221 const publicKeyArr = new Uint8Array(mem.buffer, publicKeyArrPtr, 32); 222 223 messageArr.set(message); 224 signatureArr.set(signature); 225 publicKeyArr.set(publicKey); 226 227 const res = fn.verify(signatureArrPtr, messageArrPtr, messageLen, publicKeyArrPtr) === 1; 228 229 // Free used memory on wasm side 230 fn._free(messageArrPtr); 231 fn._free(signatureArrPtr); 232 fn._free(publicKeyArrPtr); 233 234 return res; 235 } 236 237 export async function keyExchange( 238 theirPublicKey: number[] | PublicKey | undefined, 239 ourSecretKey: number[] | SecretKey 240 ): Promise<Buffer> { 241 242 const fn = (await Module).exports; 243 const mem = (await Module).memory; 244 245 // Sanitization and sanity checking 246 if (Array.isArray(theirPublicKey)) theirPublicKey = Buffer.from(theirPublicKey); 247 if (Array.isArray(ourSecretKey)) ourSecretKey = Buffer.from(ourSecretKey); 248 if (!isPublicKey(theirPublicKey)) throw new Error('Invalid public key'); 249 if (!isSecretKey(ourSecretKey)) throw new Error('Invalid secret key'); 250 251 // Allocate memory on the wasm side to transfer variables 252 const sharedSecretArrPtr = fn._malloc(32); 253 const sharedSecretArr = new Uint8Array(mem.buffer, sharedSecretArrPtr, 32); 254 const publicKeyArrPtr = fn._malloc(32); 255 const publicKeyArr = new Uint8Array(mem.buffer, publicKeyArrPtr, 32); 256 const secretKeyArrPtr = fn._malloc(64); 257 const secretKeyArr = new Uint8Array(mem.buffer, secretKeyArrPtr, 64); 258 259 publicKeyArr.set(theirPublicKey); 260 secretKeyArr.set(ourSecretKey); 261 262 fn.key_exchange(sharedSecretArrPtr, publicKeyArrPtr, secretKeyArrPtr); 263 264 // Free used memory on wasm side 265 fn._free(sharedSecretArrPtr); 266 fn._free(publicKeyArrPtr); 267 fn._free(secretKeyArrPtr); 268 269 return Buffer.from(sharedSecretArr); 270 } 271 272 export default KeyPair;