Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Batch sign/verify #142

Open
mkhraisha opened this issue May 12, 2023 · 4 comments
Open

Batch sign/verify #142

mkhraisha opened this issue May 12, 2023 · 4 comments

Comments

@mkhraisha
Copy link

The current sign/ verify function take in a signer object with a sign function. This works great for a custom signer signing a single credential however many HSMs support batch signing, it would be great if the library can support something to the effect of
issueCredentials([unsignedCreds]) where the suite with the signer object takes in an array of these unsigned credentials and signs them.

@dlongley
Copy link
Member

dlongley commented May 12, 2023

@mkhraisha,

It sounds like the goal above is not to simply do something like this:

const vcs = await Promise.all(credentials.map(c => issue(c, ...)));

But to, instead, capture the bytes from each credential that is ultimately to be signed (i.e., usually some concatenated hashes) in a queue so it can be sent in a single request to an HSM for batch processing. Have I understood correctly?

If so, then this implies, to me, that a signer implementation that is passed in the creation of a suite (for example) could do this queuing internally without any changes here or elsewhere in the stack. The signer could then be used in one or more suites that are passed in multiple, concurrent issuance calls (e.g., like in the above code).

Such a signer.sign() implementation could do internal batching in a similar way that database drivers do today with N requests sent over M pooled connections -- without exposing those details at higher layers. This might even result in better performance characteristics with potentially more sources naturally feeding the queue(s). What do you think?

@mkhraisha
Copy link
Author

The goal is to send a single request to an HSM that states please sign X credentials.

const vcs = await Promise.all(credentials.map(c => issue(c, ...)));

I think your comment is on the right track, and it definitely belongs to the Signer.sign() function.

I see that the sign function has an id field is that correlated to the credential in any way or is it an arbitrary id that can be used for anything?

I guess what I'm trying to wrap my head around is assuming two requests to sign 5 credentials come in, and we want to send them all in one call to the HSM. It is trivial to set up a basic batching on the signer.sign function that says "wait for X credentials before submitting or a timeout of X before sending out the request". What I'm struggling with is what would the signer.sign function return from the sign call, and how can it associate it to each credential?

@dlongley
Copy link
Member

dlongley commented May 25, 2023

@mkhraisha,

I see that the sign function has an id field is that correlated to the credential in any way or is it an arbitrary id that can be used for anything?

The id field is for identifying the verification method / key ID. So this would be the same only when used with the same key.

What I'm struggling with is what would the signer.sign function return from the sign call, and how can it associate it to each credential?

It would still return the same thing it does now -- the batching would all be handled internally. Some quick back of the napkin (do not use for real, of course) pseudo code:

batchSigner = createBatchSigner({id, /* whatever else you need */});

function createBatchSigner({id, ...}) {
  // really, just use something from:
  // https://www.npmjs.com/package/promise-fun
  let queue = [];
  
  return {
    id,
    async sign(x) {
      return scheduleSign(x);
    }
  };

  async function scheduleSign(x) {
    let resolve;
    promise = new Promise(r => resolve = r);
    queue.push({x, resolve});
    
    if(isTimeToSendOrBatchFullOrWhatever()) {
      sendBatch();
    } else {
      setTimeout(() => sendBatch(), waitTime);
    }
    
    // notably returns the same thing as usual: a promise
    // that resolves to a signature on `x`
    return promise;
  }
  
  async function sendBatch() {
    // clear queue for next batch
    const batch = queue;
    queue = [];
  
    /* send batch to HSM, it returns results in order */
    const allX = batch.map(({x}) => x);
    const results = await doHsmBatchSign(allX);
    // resolve all the pending promises in this batch
    // with their matched up result
    batch.forEach(({resolve}, i) => resolve(results[i]));
  }
}

@mkhraisha
Copy link
Author

That makes a ton of sense, it probably should be cleaned up and added somewhere to the readme? I feel like many people would be looking to batch sign and a starting point would help them out quite a bit.
Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants