From 746e50597c97c5ec1682b87c28e27fb83a28477e Mon Sep 17 00:00:00 2001 From: Thibaut Le Guilly Date: Wed, 24 Nov 2021 09:14:53 +0900 Subject: [PATCH] Add adaptor point computation optimizations (#154) --- NumericOutcomeCompression.md | 77 ++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/NumericOutcomeCompression.md b/NumericOutcomeCompression.md index d44b9d0..8922824 100644 --- a/NumericOutcomeCompression.md +++ b/NumericOutcomeCompression.md @@ -35,6 +35,9 @@ where attestations are ignored. * [Counting Adaptor Points](#counting-adaptor-points) * [Optimizations](#optimizations) * [Algorithms](#algorithms) + * [Adaptor Point Computation Optimizations](#adaptor-point-computation-optimizations) + * [Pre-computing](#pre-computing) + * [Memoization](#memoization) * [Reference Implementations](#reference-implementations) * [Authors](#authors) @@ -49,6 +52,8 @@ points is used: `s(1..m) * G = (s1 + s2 + ... + sm) * G = s1 * G + s2 * G + ... When the oracle broadcasts its `n` attestations `s1, ..., sn`, the corresponding aggreate adaptor secret can be computed as `s(1..m) = s1 + s2 + ... + sm` which can be used to broadcast a corresponding CET. +Note that [optimizations](#Adaptor-Point-Computation-Optimizations) can be applied when computing signature points. + #### Rationale This design allows implementations to re-use all [transaction construction](Transactions.md) and signing code without modification @@ -370,6 +375,78 @@ def groupByIgnoringDigits(start: Long, end: Long, base: Int, numDigits: Int): Ve } ``` +## Adaptor Point Computation Optimizations + +Computing a large number of adaptor points for events with outcome composed of many digits is a computationally intensive process which accounts for a significant portion of DLC setup time. +To reduce the time and resources required, some optimizations can be applied. +For more detailed information on these optimizations please check out [this blog post](https://medium.com/crypto-garage/optimizing-numeric-outcome-dlc-creation-6d6091ac0e47). + +### Pre-computing + +The first optimization is to first compute the signature points for the possible value of each digit. +For example, for an event with outcomes decomposed in base 2 with 10 digits, first compute: +``` +S0_0 = R0 + 0 * P +S0_1 = R0 + 1 * P +S1_0 = R1 + 0 * P +S1_1 = R1 + 1 * P +... +S9_0 = R9 + 0 * P +S9_1 = R9 + 1 * P +``` + +Then compute the aggregate signature points by combining the Si_j. +For example, to compute the signature point for the value `0 0 0 0 0 0 0 0 0 0`, compute: +``` +S = S0_0 + S1_0 + ... + S9_0 +``` + +To compute another signature point, for example for the value `1 0 0 0 0 0 0 0 0 1`, compute: +``` +S' = S0_1 + S1_0 + ... + S9_1 +``` + +### Memoization + +In addition to the above pre-computation, [memoization](https://en.wikipedia.org/wiki/Memoization) can be used to further improve performance. +Note however that if using the [secp256k1-zkp library](https://github.com/ElementsProject/secp256k1-zkp) this optimization is actually not effective as adding the points in batch using only pre-computation was found to be faster. + +Taking as example an event with outcomes decomposed in base 2 with 10 digits, the algorithm would proceed as follow: +1. Compute `S0_0` for the value `0` +2. Compute `S = S0_0 + S1_0` for the value `0 0` +3. Compute `S' = S0_0 + S1_0 + S2_0` for the value `0 0 0` +... +10. Compute `S'' = S0_0 + S1_0 + ... + S8_0` for the value `0 0 0 0 0 0 0 0 0` +11. Compute `S''' = S0_0 + S1_0 + ... + S9_0` for the value `0 0 0 0 0 0 0 0 0 0` +12. Compute `S'''' = S'' + S9_1` for the value `0 0 0 0 0 0 0 0 0 1` + +A recursive implementation of this algorithm is given below: +```typescript +function computeSigPoints( + index: number, + prev: PublicKey | undefined, + noncesSigPoints: PublicKey[][], + result: PublicKey[] +): void { + for (let i = 0; i < noncesSigPoints.length; i++) { + let next = undefined + if (prev === undefined) { + next = noncesSigPoints[0][index] + } else { + next = prev.combine(noncesSigPoints[index][i]) + } + if (index < noncesSigPoints.length - 1) { + computeSigPoints(index + 1, next, noncesSigPoints, result) + } else { + result.push(next) + } + } +} + +const result: PublicKey[] = [] +computeSigPoints(0, undefined, noncesSigPoints, result) +``` + ## Reference Implementations * [bitcoin-s](https://github.com/bitcoin-s/bitcoin-s/blob/adaptor-dlc/core/src/main/scala/org/bitcoins/core/protocol/dlc/CETCalculator.scala)