supercop.ts

cross-compiled javascript implementation of ed25519 based on supercop-ref10
git clone git://git.finwo.net/lib/supercop.ts
Log | Files | Refs | README | LICENSE

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;