Add adaptor point computation optimizations (#154)

This commit is contained in:
Thibaut Le Guilly
2021-11-24 09:14:53 +09:00
committed by GitHub
parent 4992647b2c
commit 746e50597c

View File

@@ -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)