├── .eslintignore
├── .gitmodules
├── tsconfig.prod.json
├── Dockerfile
├── src
├── index.ts
├── lib
│ ├── cmodule.ts
│ ├── index.ts
│ ├── ecdh.ts
│ ├── generator.ts
│ ├── memory.ts
│ ├── pedersen.ts
│ ├── interface.ts
│ ├── surjectionproof.ts
│ ├── rangeproof.ts
│ ├── musig.ts
│ └── ecc.ts
├── test
│ ├── libs.spec.ts
│ ├── ecdh.spec.ts
│ ├── generator.spec.ts
│ ├── pedersen.spec.ts
│ ├── fixtures
│ │ ├── ecdh.json
│ │ ├── generator.json
│ │ ├── pedersen.json
│ │ ├── musig.json
│ │ └── surjectionproof.json
│ ├── surjectionproof.spec.ts
│ ├── rangeproof.spec.ts
│ ├── musig.spec.ts
│ └── ecc.spec.ts
└── main.c
├── tsconfig.prod.module.json
├── .gitignore
├── .prettierignore
├── tsconfig.module.json
├── .github
└── workflows
│ └── ci.yml
├── scripts
├── compile_wasm_docker
└── build_wasm
├── .eslintrc.json
├── LICENSE
├── README.md
├── tsconfig.json
└── package.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | src/lib/secp256k1-zkp.js
2 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "secp256k1-zkp"]
2 | path = secp256k1-zkp
3 | url = https://github.com/ElementsProject/secp256k1-zkp.git
4 |
--------------------------------------------------------------------------------
/tsconfig.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "exclude": [
4 | "node_modules/**",
5 | "src/test"
6 | ]
7 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM emscripten/emsdk:3.1.40
2 |
3 | RUN apt-get update
4 | RUN apt-get install dh-autoreconf -y
5 |
6 | WORKDIR /build
7 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { secp256k1Function } from './lib';
2 |
3 | export * from './lib/interface';
4 | export default secp256k1Function;
5 |
--------------------------------------------------------------------------------
/tsconfig.prod.module.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.module",
3 | "exclude": [
4 | "node_modules/**",
5 | "src/test"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/*
2 | .vscode/
3 | .nyc_output
4 | build
5 | node_modules
6 | src/**.js
7 | coverage
8 | *.log
9 | package-lock.json
10 | dist
11 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # package.json is formatted by package managers, so we ignore it here
2 | package.json
3 | # ignore the generated file
4 | src/lib/secp256k1-zkp.js
5 |
--------------------------------------------------------------------------------
/tsconfig.module.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "outDir": "build/module",
6 | "module": "esnext"
7 | },
8 | "exclude": [
9 | "node_modules/**"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/src/lib/cmodule.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import lib from './secp256k1-zkp.js';
4 |
5 | export interface CModule extends EmscriptenModule {
6 | ccall: typeof ccall;
7 | setValue: typeof setValue;
8 | getValue: typeof getValue;
9 | }
10 |
11 | export async function loadSecp256k1ZKP(): Promise {
12 | return lib() as Promise;
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/libs.spec.ts:
--------------------------------------------------------------------------------
1 | import anyTest, { TestInterface } from 'ava';
2 | import { BIP32Factory } from 'bip32';
3 | import { ECPairFactory } from 'ecpair';
4 |
5 | import secp256k1 from '../index';
6 | import { Secp256k1ZKP } from '../lib/interface';
7 |
8 | const test = anyTest as TestInterface;
9 |
10 | test.before(async (t) => {
11 | t.context = await secp256k1();
12 | });
13 |
14 | test('bitcoinjs-lib BIP32Factory', (t) => {
15 | t.notThrows(() => BIP32Factory(t.context.ecc));
16 | });
17 |
18 | test('bitcoinjs-lib ECPairFactory', (t) => {
19 | t.notThrows(() => ECPairFactory(t.context.ecc));
20 | });
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches: [master]
5 | pull_request:
6 | branches: [master]
7 | jobs:
8 | build:
9 | name: Build, lint, and test on Node ${{ matrix.node }}
10 |
11 | runs-on: ubuntu-latest
12 | strategy:
13 | matrix:
14 | node: ["14.x", "16.x"]
15 |
16 | steps:
17 | - name: Checkout repo
18 | uses: actions/checkout@v2
19 |
20 | - name: Use Node ${{ matrix.node }}
21 | uses: actions/setup-node@v2
22 | with:
23 | node-version: ${{ matrix.node }}
24 |
25 | - name: Install dependencies
26 | run: yarn install
27 |
28 | - name: Lint & Test
29 | run: yarn test
30 |
--------------------------------------------------------------------------------
/src/test/ecdh.spec.ts:
--------------------------------------------------------------------------------
1 | import anyTest, { TestInterface } from 'ava';
2 |
3 | import { loadSecp256k1ZKP } from '../lib/cmodule';
4 | import { ecdh } from '../lib/ecdh';
5 | import { Secp256k1ZKP } from '../lib/interface';
6 |
7 | import fixtures from './fixtures/ecdh.json';
8 |
9 | const test = anyTest as TestInterface<{ ecdh: Secp256k1ZKP['ecdh'] }>;
10 |
11 | test.before(async (t) => {
12 | const cModule = await loadSecp256k1ZKP();
13 | t.context = { ecdh: ecdh(cModule) };
14 | });
15 |
16 | test('ecdh', (t) => {
17 | const { ecdh } = t.context;
18 |
19 | fixtures.ecdh.forEach((f) => {
20 | const pubkey = Buffer.from(f.pubkey, 'hex');
21 | const scalar = Buffer.from(f.scalar, 'hex');
22 | t.is(Buffer.from(ecdh(pubkey, scalar)).toString('hex'), f.expected);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | import { loadSecp256k1ZKP } from './cmodule';
2 | import { ecc } from './ecc';
3 | import { ecdh } from './ecdh';
4 | import { generator } from './generator';
5 | import { Secp256k1ZKP } from './interface';
6 | import { musig } from './musig';
7 | import { pedersen } from './pedersen';
8 | import { rangeproof } from './rangeproof';
9 | import { surjectionproof } from './surjectionproof';
10 |
11 | export const secp256k1Function = async (): Promise => {
12 | const cModule = await loadSecp256k1ZKP();
13 | return {
14 | ecdh: ecdh(cModule),
15 | ecc: ecc(cModule),
16 | musig: musig(cModule),
17 | pedersen: pedersen(cModule),
18 | generator: generator(cModule),
19 | rangeproof: rangeproof(cModule),
20 | surjectionproof: surjectionproof(cModule),
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/src/lib/ecdh.ts:
--------------------------------------------------------------------------------
1 | import { CModule } from './cmodule';
2 | import { Secp256k1ZKP } from './interface';
3 | import Memory from './memory';
4 |
5 | export function ecdh(cModule: CModule): Secp256k1ZKP['ecdh'] {
6 | return function (pubkey: Uint8Array, scalar: Uint8Array): Uint8Array {
7 | const memory = new Memory(cModule);
8 | const output = memory.malloc(32);
9 | const ret = cModule.ccall(
10 | 'ecdh',
11 | 'number',
12 | ['number', 'number', 'number'],
13 | [output, memory.charStar(pubkey), memory.charStar(scalar)]
14 | );
15 |
16 | if (ret === 1) {
17 | const out = new Uint8Array(cModule.HEAPU8.subarray(output, output + 32));
18 | memory.free();
19 | return out;
20 | } else {
21 | memory.free();
22 | throw new Error('secp256k1_ecdh');
23 | }
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/scripts/compile_wasm_docker:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Build the container
4 | docker build -t secp256k1-wasm .
5 | # Run the container
6 | docker run --name linux-build --entrypoint=sh -d -i secp256k1-wasm
7 |
8 | # Copy the secp256k1 folder inside the container
9 | docker cp ./secp256k1-zkp/. linux-build:/build/secp256k1-zkp
10 | # Copy the C wrapper
11 | docker cp ./src/main.c linux-build:/build
12 | # Copy the custom build script inside the container
13 | docker cp ./scripts/build_wasm linux-build:/build
14 |
15 | # Compile to wasm target
16 | docker exec linux-build bash build_wasm
17 |
18 | # Copy the artifacts from the container to local directory
19 | rm -rf src/lib/secp256k1-zkp.js
20 | docker cp linux-build:/build/dist/secp256k1-zkp.js ./src/lib
21 |
22 | docker kill linux-build
23 | docker rm linux-build
24 | #docker rmi secp256k1-wasm
25 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "parserOptions": { "project": "./tsconfig.json" },
5 | "env": { "es6": true },
6 | "ignorePatterns": ["node_modules", "build", "coverage"],
7 | "plugins": ["import", "eslint-comments"],
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:eslint-comments/recommended",
11 | "plugin:@typescript-eslint/recommended",
12 | "plugin:import/typescript",
13 | "prettier",
14 | "prettier/@typescript-eslint"
15 | ],
16 | "globals": { "BigInt": true, "console": true, "WebAssembly": true },
17 | "rules": {
18 | "eol-last": "error",
19 | "@typescript-eslint/explicit-module-boundary-types": "off",
20 | "eslint-comments/disable-enable-pair": [
21 | "error",
22 | { "allowWholeFile": true }
23 | ],
24 | "eslint-comments/no-unused-disable": "error",
25 | "import/order": [
26 | "error",
27 | { "newlines-between": "always", "alphabetize": { "order": "asc" } }
28 | ],
29 | "sort-imports": [
30 | "error",
31 | { "ignoreDeclarationSort": true, "ignoreCase": true }
32 | ]
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Vulpem Ventures contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/src/test/generator.spec.ts:
--------------------------------------------------------------------------------
1 | import anyTest, { TestInterface } from 'ava';
2 |
3 | import { loadSecp256k1ZKP } from '../lib/cmodule';
4 | import { generator } from '../lib/generator';
5 | import { Secp256k1ZKP } from '../lib/interface';
6 |
7 | import fixtures from './fixtures/generator.json';
8 |
9 | const test = anyTest as TestInterface;
10 |
11 | test.before(async (t) => {
12 | const cModule = await loadSecp256k1ZKP();
13 | t.context = generator(cModule);
14 | });
15 |
16 | test('generate', (t) => {
17 | const { generate } = t.context;
18 |
19 | fixtures.generate.forEach((f) => {
20 | const seed = new Uint8Array(Buffer.from(f.seed, 'hex'));
21 | t.is(Buffer.from(generate(seed)).toString('hex'), f.expected);
22 | });
23 | });
24 |
25 | test('generate_blinded', (t) => {
26 | const { generateBlinded } = t.context;
27 |
28 | fixtures.generateBlinded.forEach((f) => {
29 | const key = new Uint8Array(Buffer.from(f.key, 'hex'));
30 | const blindingKey = new Uint8Array(Buffer.from(f.blind, 'hex'));
31 | t.is(
32 | Buffer.from(generateBlinded(key, blindingKey)).toString('hex'),
33 | f.expected
34 | );
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/src/test/pedersen.spec.ts:
--------------------------------------------------------------------------------
1 | import anyTest, { TestInterface } from 'ava';
2 |
3 | import { loadSecp256k1ZKP } from '../lib/cmodule';
4 | import { Secp256k1ZKP } from '../lib/interface';
5 | import { pedersen } from '../lib/pedersen';
6 |
7 | import fixtures from './fixtures/pedersen.json';
8 |
9 | const test = anyTest as TestInterface;
10 |
11 | test.before(async (t) => {
12 | const cModule = await loadSecp256k1ZKP();
13 | t.context = pedersen(cModule);
14 | });
15 |
16 | test('commitment', (t) => {
17 | const { commitment } = t.context;
18 |
19 | fixtures.commitment.forEach((f) => {
20 | const blinder = new Uint8Array(Buffer.from(f.blinder, 'hex'));
21 | const generator = new Uint8Array(Buffer.from(f.generator, 'hex'));
22 | t.is(
23 | Buffer.from(commitment(f.value, generator, blinder)).toString('hex'),
24 | f.expected
25 | );
26 | });
27 | });
28 |
29 | test('blind generator blind sum', (t) => {
30 | const { blindGeneratorBlindSum } = t.context;
31 |
32 | fixtures.blindGeneratorBlindSum.forEach((f) => {
33 | const assetBlinders = f.assetBlinders.map((b) => Buffer.from(b, 'hex'));
34 | const valueBlinders = f.valueBlinders.map((b) => Buffer.from(b, 'hex'));
35 | t.is(
36 | Buffer.from(
37 | blindGeneratorBlindSum(
38 | f.values,
39 | assetBlinders,
40 | valueBlinders,
41 | f.nInputs
42 | )
43 | ).toString('hex'),
44 | f.expected
45 | );
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/vulpemventures/secp256k1-zkp)
2 | [](https://github.com/feross/standard)
3 |
4 | # secp256k1-zkp
5 |
6 | Essential methods of the secp256k1-zkp lib exported to JS for handling Elements confidential transactions
7 |
8 | ## Installation
9 |
10 | Use the package manager yarn or npm to install secp256k1-zkp.
11 |
12 | ```bash
13 | yarn add @vulpemventures/secp256k1-zkp
14 |
15 | npm install @vulpemventures/secp256k1-zkp
16 |
17 | ```
18 |
19 | ## Usage
20 |
21 | The library is exported as a function that returns a promise.
22 |
23 | ```ts
24 | import secp256k1 from '@vulpemventures/secp256k1-zkp'
25 |
26 | async function main() {
27 | const lib = await secp256k1();
28 | // use the library functions
29 | }
30 |
31 | ```
32 |
33 | ## Documentation
34 |
35 | Typedoc html page is available via:
36 |
37 | ```bash
38 | yarn doc
39 | ```
40 |
41 | ## Development
42 |
43 | Fetch submodules
44 |
45 | ```bash
46 | git submodule update --init --recursive
47 | ```
48 |
49 | Install dependencies
50 |
51 | ```bash
52 | yarn install
53 | ```
54 |
55 | Compile the WASM (writes ./lib/secp256k1-zkp.js file)
56 |
57 | ```bash
58 | yarn compile
59 | ```
60 |
61 | Build the library
62 |
63 | ```bash
64 | yarn build
65 | ```
66 |
67 | Run tests
68 |
69 | ```bash
70 | yarn test
71 | ```
72 |
73 | ## Contributing
74 |
75 | Pull requests are welcome. For major changes, please open an issue first
76 | to discuss what you would like to change.
77 |
78 | Please make sure to update tests as appropriate.
79 |
80 | ## License
81 |
82 | [MIT](https://choosealicense.com/licenses/mit/)
--------------------------------------------------------------------------------
/src/lib/generator.ts:
--------------------------------------------------------------------------------
1 | import { CModule } from './cmodule';
2 | import { Secp256k1ZKP } from './interface';
3 | import Memory from './memory';
4 |
5 | function generate(cModule: CModule): Secp256k1ZKP['generator']['generate'] {
6 | return function (seed: Uint8Array) {
7 | if (!seed || !(seed instanceof Uint8Array) || seed.length !== 32) {
8 | throw new TypeError('seed must be a Uint8Array of 32 bytes');
9 | }
10 | const memory = new Memory(cModule);
11 |
12 | const output = memory.malloc(33);
13 |
14 | const ret = cModule.ccall(
15 | 'generator_generate',
16 | 'number',
17 | ['number', 'number'],
18 | [output, memory.charStar(seed)]
19 | );
20 | if (ret === 1) {
21 | const out = new Uint8Array(cModule.HEAPU8.subarray(output, output + 33));
22 | memory.free();
23 | return out;
24 | }
25 | memory.free();
26 | throw new Error('secp256k1_generator_generate');
27 | };
28 | }
29 |
30 | function generateBlinded(
31 | cModule: CModule
32 | ): Secp256k1ZKP['generator']['generateBlinded'] {
33 | return function (key, blinder) {
34 | if (!key || !(key instanceof Uint8Array) || key.length !== 32)
35 | throw new TypeError('key must be a Uint8Array of 32 bytes');
36 | if (!blinder || !(blinder instanceof Uint8Array) || blinder.length !== 32)
37 | throw new TypeError('blind must be a Uint8Array of 32 bytes');
38 |
39 | const memory = new Memory(cModule);
40 |
41 | const output = memory.malloc(33);
42 |
43 | const ret = cModule.ccall(
44 | 'generator_generate_blinded',
45 | 'number',
46 | ['number', 'number', 'number'],
47 | [output, memory.charStar(key), memory.charStar(blinder)]
48 | );
49 | if (ret === 1) {
50 | const out = new Uint8Array(cModule.HEAPU8.subarray(output, output + 33));
51 | memory.free();
52 | return out;
53 | } else {
54 | memory.free();
55 | throw new Error('secp256k1_generator_generate_blinded');
56 | }
57 | };
58 | }
59 |
60 | export function generator(cModule: CModule): Secp256k1ZKP['generator'] {
61 | return {
62 | generate: generate(cModule),
63 | generateBlinded: generateBlinded(cModule),
64 | };
65 | }
66 |
--------------------------------------------------------------------------------
/src/lib/memory.ts:
--------------------------------------------------------------------------------
1 | import Long from 'long';
2 |
3 | import { CModule } from './cmodule';
4 |
5 | interface MemoryI {
6 | charStarToUint8(ptr: number, size: number): Uint8Array;
7 | malloc(size: number): number;
8 | charStar(buffer: Uint8Array): number;
9 | charStarArray(buffers: Uint8Array[]): number;
10 | longIntStarArray(values: Long[]): number;
11 | free(): void;
12 | }
13 |
14 | export default class Memory implements MemoryI {
15 | private toFree: number[] = [];
16 |
17 | constructor(private cModule: CModule) {}
18 |
19 | charStarToUint8(ptr: number, size: number): Uint8Array {
20 | return new Uint8Array(this.cModule.HEAPU8.subarray(ptr, ptr + size));
21 | }
22 |
23 | malloc(size: number): number {
24 | const ret = this.cModule._malloc(size);
25 | this.toFree.push(ret);
26 | return ret;
27 | }
28 |
29 | charStar(buffer: Uint8Array): number {
30 | const ptr = this.malloc(buffer.length);
31 | for (let i = 0; i < buffer.length; i++) {
32 | this.cModule.setValue(ptr + i, buffer[i], 'i8');
33 | }
34 | return ptr;
35 | }
36 |
37 | charStarArray(buffers: Uint8Array[]): number {
38 | const arrayPtrs = this.malloc(4 * buffers.length);
39 | for (let i = 0; i < buffers.length; i++) {
40 | const ptr = this.charStar(buffers[i]);
41 | this.cModule.setValue(arrayPtrs + i * 4, ptr, 'i32');
42 | }
43 | return arrayPtrs;
44 | }
45 |
46 | longIntStarArray(values: Long[]): number {
47 | const ptr = this.malloc(8 * values.length);
48 | for (let i = 0; i < values.length; i++) {
49 | this.cModule.setValue(ptr + i * 8, values[i].low, 'i32');
50 | this.cModule.setValue(ptr + i * 8 + 4, values[i].high, 'i32');
51 | }
52 | return ptr;
53 | }
54 |
55 | readUint64Long(pointer: number): Long {
56 | return new Long(
57 | this.cModule.getValue(pointer, 'i32'),
58 | this.cModule.getValue(pointer + 4, 'i32'),
59 | true
60 | );
61 | }
62 |
63 | uint64Long(value: Long): number {
64 | const pointer = this.malloc(8);
65 | this.cModule.setValue(pointer, value.low, 'i32');
66 | this.cModule.setValue(pointer + 4, value.high, 'i32');
67 | return pointer;
68 | }
69 |
70 | free(): void {
71 | this.toFree.forEach((ptr) => this.cModule._free(ptr));
72 | this.toFree = [];
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/test/fixtures/ecdh.json:
--------------------------------------------------------------------------------
1 | {
2 | "ecdh": [
3 | {
4 | "scalar": "d90314455f64c385db12f629c2adbecc576baebdfe70905a412747a689872760",
5 | "pubkey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
6 | "expected": "f66a0881818550bcd6ad23fb80f58d01b57b1b4f2aafec5e4d3ab53a5fa5c6c6"
7 | },
8 | {
9 | "scalar": "9bf85f75c45152cf42ade21db0e1621b01d5ac5ae8f97467e8b3197b9c55ac10",
10 | "pubkey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
11 | "expected": "f7e34787897e317b304a4c5b639fb32711a76e10e8a94ca0086768be24a0c800"
12 | },
13 | {
14 | "scalar": "e4392d6ac2abd286eca682a726230af4906ae3211d00f602a674d9162d3247e8",
15 | "pubkey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
16 | "expected": "4fe7858b8886c5aa7a6152329fdf18ab46ff2fdd71f030697e53572aa287f5d1"
17 | },
18 | {
19 | "scalar": "32ba8137ca4f3fd1ae3b8180bf3f53e2f0b4c7a3c39236fb1152600c0f9950f5",
20 | "pubkey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
21 | "expected": "95fe82db4ea63bc3145c41fa17243c804090452473d443e77ec81a3a2d894a35"
22 | },
23 | {
24 | "scalar": "4e3da01ed8a8c5f29539eaf435fb9ff959fea677589df81867ad949b95a00227",
25 | "pubkey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
26 | "expected": "947096ab953d2c0123c113c4fad0494eba60b8f9be5a2a9dfc414bbb3a3d5c14"
27 | },
28 | {
29 | "scalar": "b295892920f72b4d5546eeb6e0868adc719c71a27571ccfd15179571566b5b7e",
30 | "pubkey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
31 | "expected": "b783846294b86fe637af3a885862914530d3c07e66fdfaf8093ad34ab2de1fed"
32 | },
33 | {
34 | "scalar": "469ff0439a67397e305a22b30b68cea2f8c38562a19103b973dd7589b8d8693c",
35 | "pubkey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
36 | "expected": "469c6d7382a5a22be7e42d4d77b7e9c7832dacd2278481d2b9116b3d272a6dbf"
37 | },
38 | {
39 | "scalar": "ae674cb62376a9b1bfc437484d8d5c401ccf61e9d62af08c041eab8c6b75b355",
40 | "pubkey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
41 | "expected": "4964afacdf21ee9fa801e7a854ad121cdd8d385f0ed4e5bef665c2eb05e9800d"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/scripts/build_wasm:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 |
3 | set -e
4 |
5 | # Parallelize. Default 4 jobs, check based on current available cores with cpuinfo
6 | num_jobs=4
7 | if [ -f /proc/cpuinfo ]; then
8 | num_jobs=$(grep ^processor /proc/cpuinfo | wc -l)
9 | fi
10 |
11 | # optmization level
12 | OPTIMIZATION_LEVEL=s
13 | # C functions to export to Javascript
14 | EXPORTED_RUNTIME_METHODS="['getValue', 'setValue', 'ccall']"
15 | EXPORTED_FUNCTIONS="['_secp256k1_ecmult_gen_prec_table', '_secp256k1_pre_g', '_free', '_malloc', '_ecdh', '_generator_generate', '_generator_generate_blinded', '_pedersen_blind_generator_blind_sum', '_pedersen_commitment', '_rangeproof_sign', '_rangeproof_info', '_rangeproof_verify', '_rangeproof_rewind', '_surjectionproof_initialize', '_surjectionproof_generate', '_surjectionproof_verify', '_ec_seckey_negate', '_ec_seckey_tweak_add', '_ec_seckey_tweak_sub', '_ec_seckey_tweak_mul', '_ec_is_point', '_ec_point_compress', '_ec_point_from_scalar', '_ec_x_only_point_tweak_add', '_ec_sign_ecdsa', '_ec_verify_ecdsa', '_ec_sign_schnorr', '_ec_verify_schnorr', '_ec_seckey_verify', '_ec_point_add_scalar', '_musig_pubkey_agg', '_musig_nonce_gen', '_musig_nonce_agg', '_musig_nonce_process', '_musig_partial_sign', '_musig_partial_sig_verify', '_musig_partial_sig_agg', '_musig_pubkey_xonly_tweak_add']"
16 |
17 | SECP256K1_SOURCE_DIR=secp256k1-zkp
18 |
19 | cd ${SECP256K1_SOURCE_DIR}
20 |
21 | # run autogen
22 | ./autogen.sh
23 |
24 | # Compile secp256k1 to bitcode
25 | emconfigure ./configure --enable-tests=no --enable-exhaustive-tests=no --enable-benchmark=no --enable-module-rangeproof=yes --enable-module-surjectionproof=yes --enable-experimental=yes --enable-module-generator=yes --enable-module-schnorrsig=yes --enable-module-extrakeys=yes --enable-module-ecdh=yes --enable-module-musig=yes
26 | emmake make -j $num_jobs
27 |
28 | # go back to the root folder
29 | cd ..
30 |
31 | # Create a folder for artifacts
32 | mkdir -p dist
33 |
34 | # Compile to wasm
35 | emcc -O$OPTIMIZATION_LEVEL \
36 | -s "EXPORTED_RUNTIME_METHODS=${EXPORTED_RUNTIME_METHODS}" \
37 | -s "EXPORTED_FUNCTIONS=${EXPORTED_FUNCTIONS}" \
38 | -s NO_FILESYSTEM=1 \
39 | -s MODULARIZE=1 \
40 | -s SINGLE_FILE=1 \
41 | -s ALLOW_MEMORY_GROWTH=1 \
42 | -I${SECP256K1_SOURCE_DIR}/include \
43 | ${SECP256K1_SOURCE_DIR}/src/libsecp256k1_la-secp256k1.o \
44 | ${SECP256K1_SOURCE_DIR}/src/libsecp256k1_precomputed_la-precomputed_ecmult.o \
45 | ${SECP256K1_SOURCE_DIR}/src/libsecp256k1_precomputed_la-precomputed_ecmult_gen.o \
46 | ./main.c \
47 | -o ./dist/secp256k1-zkp.js
48 |
--------------------------------------------------------------------------------
/src/test/surjectionproof.spec.ts:
--------------------------------------------------------------------------------
1 | import anyTest, { TestInterface } from 'ava';
2 |
3 | import { loadSecp256k1ZKP } from '../lib/cmodule';
4 | import { Secp256k1ZKP } from '../lib/interface';
5 | import { surjectionproof } from '../lib/surjectionproof';
6 |
7 | import fixtures from './fixtures/surjectionproof.json';
8 |
9 | const test = anyTest as TestInterface;
10 |
11 | test.before(async (t) => {
12 | const cModule = await loadSecp256k1ZKP();
13 | t.context = surjectionproof(cModule);
14 | });
15 |
16 | test('initialize proof', (t) => {
17 | const { initialize } = t.context;
18 |
19 | fixtures.initialize.forEach((f) => {
20 | const seed = new Uint8Array(Buffer.from(f.seed, 'hex'));
21 | const inputTags = f.inputTags.map(
22 | (t) => new Uint8Array(Buffer.from(t, 'hex'))
23 | );
24 | const outputTag = new Uint8Array(Buffer.from(f.outputTag, 'hex'));
25 | const res = initialize(inputTags, outputTag, f.maxIterations, seed);
26 | t.is(Buffer.from(res.proof).toString('hex'), f.expected.proof);
27 | t.is(res.inputIndex, f.expected.inputIndex);
28 | });
29 | });
30 |
31 | test('generate proof', (t) => {
32 | const { generate } = t.context;
33 |
34 | fixtures.generate.forEach((f) => {
35 | const proof = new Uint8Array(Buffer.from(f.proof, 'hex'));
36 | const ephemeralInputTags = f.ephemeralInputTags.map(
37 | (v) => new Uint8Array(Buffer.from(v, 'hex'))
38 | );
39 | const ephemeralOutputTag = new Uint8Array(
40 | Buffer.from(f.ephemeralOutputTag, 'hex')
41 | );
42 | const inputBlindingKey = new Uint8Array(
43 | Buffer.from(f.inputBlindingKey, 'hex')
44 | );
45 | const outputBlindingKey = new Uint8Array(
46 | Buffer.from(f.outputBlindingKey, 'hex')
47 | );
48 | const res = generate(
49 | proof,
50 | ephemeralInputTags,
51 | ephemeralOutputTag,
52 | f.inputIndex,
53 | inputBlindingKey,
54 | outputBlindingKey
55 | );
56 | t.is(Buffer.from(res).toString('hex'), f.expectedProof);
57 | });
58 | });
59 |
60 | test('verify proof', (t) => {
61 | const { verify } = t.context;
62 |
63 | fixtures.verify.forEach((f) => {
64 | const proof = new Uint8Array(Buffer.from(f.proof, 'hex'));
65 | const ephemeralInputTags = f.ephemeralInputTags.map(
66 | (v) => new Uint8Array(Buffer.from(v, 'hex'))
67 | );
68 | const ephemeralOutputTag = new Uint8Array(
69 | Buffer.from(f.ephemeralOutputTag, 'hex')
70 | );
71 | t.is(verify(proof, ephemeralInputTags, ephemeralOutputTag), f.expected);
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "incremental": true,
4 | "target": "es2017",
5 | "outDir": "build/main",
6 | "rootDir": "src",
7 | "moduleResolution": "node",
8 | "module": "commonjs",
9 | "declaration": true,
10 | "inlineSourceMap": true,
11 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
12 | "resolveJsonModule": true /* Include modules imported with .json extension. */,
13 | "allowJs": true /* Allow javascript files to be compiled. */,
14 | "strict": true /* Enable all strict type-checking options. */,
15 |
16 | /* Strict Type-Checking Options */
17 | // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
18 | // "strictNullChecks": true /* Enable strict null checks. */,
19 | // "strictFunctionTypes": true /* Enable strict checking of function types. */,
20 | // "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */,
21 | // "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
22 | // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
23 |
24 | /* Additional Checks */
25 | "noUnusedLocals": true /* Report errors on unused locals. */,
26 | "noUnusedParameters": true /* Report errors on unused parameters. */,
27 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
28 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
29 |
30 | /* Debugging Options */
31 | "traceResolution": false /* Report module resolution log messages. */,
32 | "listEmittedFiles": false /* Print names of generated files part of the compilation. */,
33 | "listFiles": false /* Print names of files part of the compilation. */,
34 | "pretty": true /* Stylize errors and messages using color and context. */,
35 |
36 | /* Experimental Options */
37 | // "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
38 | // "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,
39 |
40 | "lib": ["es2017", "dom"],
41 | "types": ["node"],
42 | "typeRoots": ["node_modules/@types", "src/types"]
43 | },
44 | "include": ["src/**/*.ts"],
45 | "exclude": ["node_modules/**"],
46 | "compileOnSave": false
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/fixtures/generator.json:
--------------------------------------------------------------------------------
1 | {
2 | "generate": [
3 | {
4 | "seed": "2f019c8ef3d4bb237e99f6c0ff61c44d07bd8b55f383fdd2fa23ccb65c8117f7",
5 | "expected": "0b3b19418342d60b1f5440d7c0f00bbaf53783d62571f542d45e6e5bfb33a65774"
6 | }
7 | ],
8 | "generateBlinded": [
9 | {
10 | "key": "2f019c8ef3d4bb237e99f6c0ff61c44d07bd8b55f383fdd2fa23ccb65c8117f7",
11 | "blind": "c6b8afcef6be3f8585ec7beb7fe89fea0ba9be47ad05f95b0c1a359275603b10",
12 | "expected": "0b1079e313bbcf43949b008bbc8630819c8c9fea22c118be2c9c63c31233e53454"
13 | },
14 | {
15 | "key": "dff976a27fe72de1fb0a500cf0ccaa20c999a01fc38a72c68a7bdd3057751c45",
16 | "blind": "554d29f030745c9e13e50a691858b84b3d4a8fb45e6ac21fdaa8b33925e4e104",
17 | "expected": "0b8f397b11aa9d12d583ef58424fd863a6eaef6e03976d698911647272434c9996"
18 | },
19 | {
20 | "key": "907ad41fb6be3c2c85c6862dd26ae862ec693c10366b41cd521a40fdb396f645",
21 | "blind": "020c1d24fb7a88deb0688ee2240824e711397be11fa00172e153f97ed7413dcb",
22 | "expected": "0b2656c7f5a06dc84153977533285fd55440ff00e89fc880727129c43c19cb2f4f"
23 | },
24 | {
25 | "key": "573c7c2997f74ee168e42cbb18a035c109e0eb0a2fd064ae258f8b23c61035ef",
26 | "blind": "2375651b182458303f220b98024f9b4863649dc1b6e0dd273956c85e7ed66909",
27 | "expected": "0b826a1a85d3dab96eaa2952a255afd63dedbeb665f68355fd28202e45ae466f2c"
28 | },
29 | {
30 | "key": "f1b95f20163f1f2b95a59b3d17479f9f1bfdc61c4f0b1fb131f25e86ca7ffc5d",
31 | "blind": "ea3a8b39bdc6e005fea7e73c3892273f4920033c868043180e951a256f2b24c8",
32 | "expected": "0b387bad6965190ee7407b1e248b87f44cc5585bbdb42972e65397702561204ea4"
33 | },
34 | {
35 | "key": "120a4e02786337d95807f7973b6bb60d28689642cd489164a08625ebc99d3ac8",
36 | "blind": "d7d5cc6176ff854ffdca1693e5e5c2f4dce2355eddccaae6611b70bf2b923ed4",
37 | "expected": "0af8f09ab26c0c8355e4ddac856f92a07e343dbb82fb01e620f8d5294b7e12c297"
38 | },
39 | {
40 | "key": "50ff3a4f1ac1ed9186575206c0c537c89f5e235e25a9e11a5a2022574e0431e0",
41 | "blind": "1ecdc24c78908734ed8b174f8c507fa0143bdaf17d878d4206ec618a603bf533",
42 | "expected": "0b427edeec33121e853ce58536a07a8949409ebc99b7af776575e9797ff28a69b0"
43 | },
44 | {
45 | "key": "77f9cc7cb558ae283012fe87ba6180605af7d791c59e187c304c74a604e2e5c7",
46 | "blind": "5c424f07260d21915755f5b63bac88efaa7e815c24c6edc3390ab2a47885cf73",
47 | "expected": "0a40f017ce1a48a097c50a71e805695a42a2c850b47161e9b876b9de95f2a4c371"
48 | },
49 | {
50 | "key": "556d2e0a334e0b292c968eda7169a1bbcc5de4d4335116d77d2102d9378be16e",
51 | "blind": "1e234cc0b45721462ba593a2c44e27d8df89c51506d2d1e699342e77e235457a",
52 | "expected": "0afb244420c61227d3eb0da23ad1431892966b26f0e144b7f22deed043181ebcf4"
53 | },
54 | {
55 | "key": "c3a50fdfb2fefe8c057da840c304e309440b5e85a1a4d71af6a8342e6d6dcde5",
56 | "blind": "0f8d599840f274773f602553619c74bcdc2365f2fc7efa1fb86a31e3edf935fc",
57 | "expected": "0b878a9f74ae807a5d0a3d6b2b84764f1f5e4bdd0da8d5527cb9760203f6f39dc5"
58 | }
59 | ]
60 | }
--------------------------------------------------------------------------------
/src/lib/pedersen.ts:
--------------------------------------------------------------------------------
1 | import Long from 'long';
2 |
3 | import { CModule } from './cmodule';
4 | import { Secp256k1ZKP } from './interface';
5 | import Memory from './memory';
6 |
7 | function commitment(cModule: CModule): Secp256k1ZKP['pedersen']['commitment'] {
8 | return function (value: string, generator: Uint8Array, blinder: Uint8Array) {
9 | if (
10 | !generator ||
11 | !(generator instanceof Uint8Array) ||
12 | generator.length !== 33
13 | )
14 | throw new TypeError('generator must be a Uint8Array of 33 bytes');
15 | if (!blinder || !(blinder instanceof Uint8Array) || blinder.length !== 32)
16 | throw new TypeError('blinder must be a Uint8Array of 32 bytes');
17 |
18 | const memory = new Memory(cModule);
19 |
20 | const output = memory.malloc(33);
21 | const valueLong = Long.fromString(value, true);
22 |
23 | const ret = cModule.ccall(
24 | 'pedersen_commitment',
25 | 'number',
26 | ['number', 'number', 'number', 'number'],
27 | [
28 | output,
29 | memory.uint64Long(valueLong),
30 | memory.charStar(generator),
31 | memory.charStar(blinder),
32 | ]
33 | );
34 | if (ret === 1) {
35 | const out = new Uint8Array(cModule.HEAPU8.subarray(output, output + 33));
36 | memory.free();
37 | return out;
38 | } else {
39 | memory.free();
40 | throw new Error('secp256k1_pedersen_commit');
41 | }
42 | };
43 | }
44 |
45 | function blindGeneratorBlindSum(
46 | cModule: CModule
47 | ): Secp256k1ZKP['pedersen']['blindGeneratorBlindSum'] {
48 | return function (
49 | values: string[],
50 | assetBlinders: Uint8Array[],
51 | valueBlinders: Uint8Array[],
52 | nInputs: number
53 | ) {
54 | if (
55 | !assetBlinders ||
56 | !Array.isArray(assetBlinders) ||
57 | !assetBlinders.length ||
58 | !assetBlinders.every((v) => v instanceof Uint8Array)
59 | )
60 | throw new TypeError(
61 | 'asset blinders must be a non-empty list of Uint8Array'
62 | );
63 | if (!valueBlinders || !Array.isArray(valueBlinders))
64 | throw new TypeError('value blinders must be a list of Uint8Array');
65 |
66 | const memory = new Memory(cModule);
67 |
68 | const longValues = values.map((v) => Long.fromString(v, true));
69 | const blindOut = memory.malloc(32);
70 | const ret = cModule.ccall(
71 | 'pedersen_blind_generator_blind_sum',
72 | 'number',
73 | ['number', 'number', 'number', 'number', 'number', 'number'],
74 | [
75 | memory.longIntStarArray(longValues),
76 | memory.charStarArray(assetBlinders),
77 | memory.charStarArray(valueBlinders),
78 | assetBlinders.length,
79 | nInputs,
80 | blindOut,
81 | ]
82 | );
83 | if (ret === 1) {
84 | const output = new Uint8Array(
85 | cModule.HEAPU8.subarray(blindOut, blindOut + 32)
86 | );
87 | memory.free();
88 | return output;
89 | } else {
90 | memory.free();
91 | throw new Error('secp256k1_pedersen_blind_generator_blind_sum');
92 | }
93 | };
94 | }
95 |
96 | export function pedersen(cModule: CModule): Secp256k1ZKP['pedersen'] {
97 | return {
98 | commitment: commitment(cModule),
99 | blindGeneratorBlindSum: blindGeneratorBlindSum(cModule),
100 | };
101 | }
102 |
--------------------------------------------------------------------------------
/src/test/rangeproof.spec.ts:
--------------------------------------------------------------------------------
1 | import anyTest, { TestInterface } from 'ava';
2 |
3 | import { loadSecp256k1ZKP } from '../lib/cmodule';
4 | import { Secp256k1ZKP } from '../lib/interface';
5 | import { rangeproof } from '../lib/rangeproof';
6 |
7 | import fixtures from './fixtures/rangeproof.json';
8 |
9 | const test = anyTest as TestInterface;
10 |
11 | test.before(async (t) => {
12 | const cModule = await loadSecp256k1ZKP();
13 | t.context = rangeproof(cModule);
14 | });
15 |
16 | test('proof sign', (t) => {
17 | const { sign } = t.context;
18 |
19 | fixtures.sign.forEach((f) => {
20 | const valueCommitment = new Uint8Array(
21 | Buffer.from(f.valueCommitment, 'hex')
22 | );
23 | const assetCommitment = new Uint8Array(
24 | Buffer.from(f.assetCommitment, 'hex')
25 | );
26 | const valueBlinder = new Uint8Array(Buffer.from(f.valueBlinder, 'hex'));
27 | const nonce = new Uint8Array(Buffer.from(f.valueCommitment, 'hex'));
28 | const message = new Uint8Array(Buffer.from(f.message, 'hex'));
29 | const extraCommitment = new Uint8Array(
30 | Buffer.from(f.extraCommitment, 'hex')
31 | );
32 | const proof = sign(
33 | f.value,
34 | valueCommitment,
35 | assetCommitment,
36 | valueBlinder,
37 | nonce,
38 | f.minValue,
39 | '0',
40 | '0',
41 | message,
42 | extraCommitment
43 | );
44 | t.is(Buffer.from(proof).toString('hex'), f.expected);
45 | });
46 | });
47 |
48 | test('proof info', (t) => {
49 | const { info } = t.context;
50 |
51 | fixtures.info.forEach((f) => {
52 | const proof = Buffer.from(f.proof, 'hex');
53 | const proofInfo = info(proof);
54 | t.is(proofInfo.exp, f.expected.exp);
55 | t.is(proofInfo.mantissa, f.expected.mantissa);
56 | t.is(proofInfo.minValue, f.expected.minValue);
57 | t.is(proofInfo.maxValue, f.expected.maxValue);
58 | });
59 | });
60 |
61 | test('proof verify', (t) => {
62 | const { verify } = t.context;
63 |
64 | fixtures.verify.forEach((f) => {
65 | const proof = Buffer.from(f.proof, 'hex');
66 | const valueCommitment = Buffer.from(f.valueCommitment, 'hex');
67 | const assetCommitment = Buffer.from(f.assetCommitment, 'hex');
68 | const extraCommitment = Buffer.from(f.extraCommitment, 'hex');
69 | t.is(
70 | verify(proof, valueCommitment, assetCommitment, extraCommitment),
71 | f.expected
72 | );
73 | });
74 | });
75 |
76 | test('range proof rewind', (t) => {
77 | const { rewind } = t.context;
78 |
79 | fixtures.rewind.forEach((f) => {
80 | const proof = new Uint8Array(Buffer.from(f.proof, 'hex'));
81 | const valueCommitment = new Uint8Array(
82 | Buffer.from(f.valueCommitment, 'hex')
83 | );
84 | const assetCommitment = new Uint8Array(
85 | Buffer.from(f.assetCommitment, 'hex')
86 | );
87 | const extraCommitment = new Uint8Array(
88 | Buffer.from(f.extraCommitment, 'hex')
89 | );
90 | const nonce = new Uint8Array(Buffer.from(f.valueCommitment, 'hex'));
91 | const res = rewind(
92 | proof,
93 | valueCommitment,
94 | assetCommitment,
95 | nonce,
96 | extraCommitment
97 | );
98 | t.is(res.value, f.expected.value);
99 | t.is(res.minValue, f.expected.minValue);
100 | t.is(res.maxValue, f.expected.maxValue);
101 | t.is(Buffer.from(res.message).toString('hex'), f.expected.message);
102 | t.is(Buffer.from(res.blinder).toString('hex'), f.expected.blinder);
103 | });
104 | });
105 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vulpemventures/secp256k1-zkp",
3 | "version": "3.2.1",
4 | "description": "Essential methods of the secp256k1-zkp lib exported to TS/JS for handling Elements confidential transactions",
5 | "main": "build/main/index.js",
6 | "typings": "build/main/index.d.ts",
7 | "module": "build/module/index.js",
8 | "repository": "https://github.com/vulpemventures/secp256k1-zkp.git",
9 | "license": "MIT",
10 | "keywords": [],
11 | "scripts": {
12 | "compile": "bash ./scripts/compile_wasm_docker",
13 | "build": "run-p build:*",
14 | "build:main": "tsc -p tsconfig.prod.json",
15 | "build:module": "tsc -p tsconfig.prod.module.json",
16 | "fix": "run-s fix:*",
17 | "fix:prettier": "prettier \"src/**/*.ts\" --write",
18 | "fix:lint": "eslint src --ext .ts --fix",
19 | "test": "run-s test:*",
20 | "test:build": "tsc -p tsconfig.json",
21 | "test:lint": "eslint src --ext .ts",
22 | "test:prettier": "prettier \"src/**/*.ts\" --list-different",
23 | "test:unit": "nyc --silent ava",
24 | "watch:build": "tsc -p tsconfig.json -w",
25 | "watch:test": "nyc --silent ava --watch",
26 | "cov": "run-s build test:unit cov:html cov:lcov && open-cli coverage/index.html",
27 | "cov:html": "nyc report --reporter=html",
28 | "cov:lcov": "nyc report --reporter=lcov",
29 | "cov:send": "run-s cov:lcov && codecov",
30 | "cov:check": "nyc report && nyc check-coverage --lines 100 --functions 100 --branches 100",
31 | "doc": "run-s doc:html && open-cli build/docs/index.html",
32 | "doc:html": "typedoc src/index.ts --tsconfig tsconfig.prod.module.json --out build/docs",
33 | "doc:json": "typedoc src/index.ts --tsconfig tsconfig.prod.module.json --json build/docs/typedoc.json",
34 | "doc:publish": "gh-pages -m \"[ci skip] Updates\" -d build/docs",
35 | "version": "standard-version",
36 | "reset-hard": "git clean -dfx && git reset --hard && yarn",
37 | "prepare-release": "run-s reset-hard test cov:check doc:html version doc:publish"
38 | },
39 | "engines": {
40 | "node": ">=12"
41 | },
42 | "dependencies": {
43 | "long": "^5.2.3"
44 | },
45 | "devDependencies": {
46 | "@ava/typescript": "^1.1.1",
47 | "@istanbuljs/nyc-config-typescript": "^1.0.1",
48 | "@types/emscripten": "^1.39.6",
49 | "@typescript-eslint/eslint-plugin": "^4.0.1",
50 | "@typescript-eslint/parser": "^4.0.1",
51 | "ava": "^3.12.1",
52 | "bip32": "^4.0.0",
53 | "chai": "^4.3.7",
54 | "codecov": "^3.5.0",
55 | "cspell": "^4.1.0",
56 | "cz-conventional-changelog": "^3.3.0",
57 | "ecpair": "^2.1.0",
58 | "eslint": "^7.8.0",
59 | "eslint-config-prettier": "^6.11.0",
60 | "eslint-plugin-eslint-comments": "^3.2.0",
61 | "eslint-plugin-import": "^2.22.0",
62 | "gh-pages": "^3.1.0",
63 | "npm-run-all": "^4.1.5",
64 | "nyc": "^15.1.0",
65 | "open-cli": "^6.0.1",
66 | "prettier": "^2.1.1",
67 | "standard-version": "^9.0.0",
68 | "ts-node": "^9.0.0",
69 | "typedoc": "^0.24.8",
70 | "typescript": "^4.0.2"
71 | },
72 | "files": [
73 | "build/main",
74 | "build/module",
75 | "!**/*.spec.*",
76 | "!**/*.json",
77 | "CHANGELOG.md",
78 | "LICENSE",
79 | "README.md"
80 | ],
81 | "ava": {
82 | "failFast": true,
83 | "timeout": "60s",
84 | "typescript": {
85 | "rewritePaths": {
86 | "src/": "build/main/"
87 | }
88 | },
89 | "files": [
90 | "!build/module/**"
91 | ]
92 | },
93 | "config": {
94 | "commitizen": {
95 | "path": "cz-conventional-changelog"
96 | }
97 | },
98 | "prettier": {
99 | "singleQuote": true
100 | },
101 | "nyc": {
102 | "extends": "@istanbuljs/nyc-config-typescript",
103 | "exclude": [
104 | "**/*.spec.js"
105 | ]
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/lib/interface.ts:
--------------------------------------------------------------------------------
1 | export type Ecdh = (pubkey: Uint8Array, scalar: Uint8Array) => Uint8Array;
2 |
3 | export interface Ecc {
4 | privateNegate: (key: Uint8Array) => Uint8Array;
5 | privateAdd: (key: Uint8Array, tweak: Uint8Array) => Uint8Array | null;
6 | privateSub: (key: Uint8Array, tweak: Uint8Array) => Uint8Array | null;
7 | privateMul: (key: Uint8Array, tweak: Uint8Array) => Uint8Array;
8 | isPoint: (point: Uint8Array) => boolean;
9 | isPrivate: (privatePoint: Uint8Array) => boolean;
10 | pointFromScalar: (
11 | scalar: Uint8Array,
12 | compressed?: boolean
13 | ) => Uint8Array | null;
14 | pointCompress: (point: Uint8Array, compressed?: boolean) => Uint8Array;
15 | pointAddScalar(
16 | point: Uint8Array,
17 | tweak: Uint8Array,
18 | returnCompressed?: boolean // defaults to true
19 | ): Uint8Array | null;
20 | xOnlyPointAddTweak: (
21 | point: Uint8Array,
22 | tweak: Uint8Array
23 | ) => { parity: 1 | 0; xOnlyPubkey: Uint8Array } | null;
24 | sign: (
25 | message: Uint8Array,
26 | privateKey: Uint8Array,
27 | extraEntropy?: Uint8Array
28 | ) => Uint8Array;
29 | verify: (
30 | message: Uint8Array,
31 | publicKey: Uint8Array,
32 | signature: Uint8Array,
33 | strict?: boolean
34 | ) => boolean;
35 | signSchnorr: (
36 | message: Uint8Array,
37 | privateKey: Uint8Array,
38 | extraEntropy?: Uint8Array
39 | ) => Uint8Array;
40 | verifySchnorr: (
41 | message: Uint8Array,
42 | publicKey: Uint8Array,
43 | signature: Uint8Array
44 | ) => boolean;
45 | }
46 |
47 | export interface Generator {
48 | generate: (seed: Uint8Array) => Uint8Array;
49 | generateBlinded(key: Uint8Array, blinder: Uint8Array): Uint8Array;
50 | }
51 |
52 | export interface Pedersen {
53 | commitment(
54 | value: string,
55 | generator: Uint8Array,
56 | blinder: Uint8Array
57 | ): Uint8Array;
58 | blindGeneratorBlindSum(
59 | values: Array,
60 | assetBlinders: Array,
61 | valueBlinders: Array,
62 | nInputs: number
63 | ): Uint8Array;
64 | }
65 |
66 | export interface RangeProof {
67 | info(proof: Uint8Array): {
68 | exp: string;
69 | mantissa: string;
70 | minValue: string;
71 | maxValue: string;
72 | };
73 | verify(
74 | proof: Uint8Array,
75 | valueCommitment: Uint8Array,
76 | assetCommitment: Uint8Array,
77 | extraCommit?: Uint8Array
78 | ): boolean;
79 | sign(
80 | value: string,
81 | valueCommitment: Uint8Array,
82 | assetCommitment: Uint8Array,
83 | valueBlinder: Uint8Array,
84 | nonce: Uint8Array,
85 | minValue?: string,
86 | base10Exp?: string,
87 | minBits?: string,
88 | message?: Uint8Array,
89 | extraCommit?: Uint8Array
90 | ): Uint8Array;
91 | rewind(
92 | proof: Uint8Array,
93 | valueCommitment: Uint8Array,
94 | assetCommitment: Uint8Array,
95 | nonce: Uint8Array,
96 | extraCommit?: Uint8Array
97 | ): {
98 | value: string;
99 | minValue: string;
100 | maxValue: string;
101 | blinder: Uint8Array;
102 | message: Uint8Array;
103 | };
104 | }
105 |
106 | export interface SurjectionProof {
107 | initialize: (
108 | inputTags: Array,
109 | outputTag: Uint8Array,
110 | maxIterations: number,
111 | seed: Uint8Array
112 | ) => {
113 | proof: Uint8Array;
114 | inputIndex: number;
115 | };
116 | generate: (
117 | proof: Uint8Array,
118 | inputTags: Array,
119 | outputTag: Uint8Array,
120 | inputIndex: number,
121 | inputBlindingKey: Uint8Array,
122 | outputBlindingKey: Uint8Array
123 | ) => Uint8Array;
124 | verify: (
125 | proof: Uint8Array,
126 | inputTags: Array,
127 | outputTag: Uint8Array
128 | ) => boolean;
129 | }
130 |
131 | export interface Musig {
132 | pubkeyAgg(pubKeys: Array): {
133 | aggPubkey: Uint8Array;
134 | keyaggCache: Uint8Array;
135 | };
136 | nonceGen(
137 | sessionId: Uint8Array,
138 | pubKey: Uint8Array
139 | ): {
140 | pubNonce: Uint8Array;
141 | secNonce: Uint8Array;
142 | };
143 | nonceAgg(pubNonces: Array): Uint8Array;
144 | nonceProcess(
145 | nonceAgg: Uint8Array,
146 | msg: Uint8Array,
147 | keyaggCache: Uint8Array
148 | ): Uint8Array;
149 | partialSign(
150 | secNonce: Uint8Array,
151 | secKey: Uint8Array,
152 | keyaggCache: Uint8Array,
153 | session: Uint8Array
154 | ): Uint8Array;
155 | partialVerify(
156 | partialSig: Uint8Array,
157 | pubNonce: Uint8Array,
158 | pubKey: Uint8Array,
159 | keyaggCache: Uint8Array,
160 | session: Uint8Array
161 | ): boolean;
162 | partialSigAgg(
163 | session: Uint8Array,
164 | partialSigs: Array
165 | ): Uint8Array;
166 | pubkeyXonlyTweakAdd(
167 | keyaggCache: Uint8Array,
168 | tweak: Uint8Array,
169 | compress?: boolean
170 | ): {
171 | pubkey: Uint8Array;
172 | keyaggCache: Uint8Array;
173 | };
174 | }
175 |
176 | export interface Secp256k1ZKP {
177 | ecdh: Ecdh;
178 | ecc: Ecc;
179 | musig: Musig;
180 | surjectionproof: SurjectionProof;
181 | rangeproof: RangeProof;
182 | pedersen: Pedersen;
183 | generator: Generator;
184 | }
185 |
--------------------------------------------------------------------------------
/src/test/fixtures/pedersen.json:
--------------------------------------------------------------------------------
1 | {
2 | "commitment": [
3 | {
4 | "blinder": "75da7cf9cd78da579500bd51ebf89e3985e85e1343f597c2d8e82de79d725230",
5 | "value": "4290671282",
6 | "generator": "0b50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0",
7 | "expected": "085ddb6d7b02734fc7e31d569850bcf1253fe1215bb41820d07b7fe8daefc11142"
8 | },
9 | {
10 | "blinder": "c3f39f3654e205700ec25e5ad67f51628afba90ed9101464782787cf65bc7096",
11 | "value": "2623743551",
12 | "generator": "0b50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0",
13 | "expected": "08cae771a459cb2fd78711c11bf69d3b9758f1432b08a42c8d0f029d32a2a0ff5b"
14 | },
15 | {
16 | "blinder": "8f454a647e69516779b9ce4eb6b00d67f1328907710c31ffde1ba3d59e3485de",
17 | "value": "856618978",
18 | "generator": "0b50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0",
19 | "expected": "09e0c8cf6f1277cd0a60c52693695dbc970e5d3575c380e15032a764a6a51120bd"
20 | },
21 | {
22 | "blinder": "b0eeee57cf26a4725e8b82f4ce379b5568cf36641ead98b3c55a64c87c3f6cc2",
23 | "value": "7306745478",
24 | "generator": "0b50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0",
25 | "expected": "08122456a30fcf79c53cf637d63d9048bded20c7e6773557bc6ae156e901cfce62"
26 | },
27 | {
28 | "blinder": "3abaa6acad7da4a8f498330c8e7cfd52cb7db0b64eb9647b9e8d5a012e1ec96e",
29 | "value": "4829563055",
30 | "generator": "0b50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0",
31 | "expected": "0902cc869bfcbcddfb3a1cb208b1732d2504b0ab50aea65b465bb5bd9ee4952ab5"
32 | }
33 | ],
34 | "blindGeneratorBlindSum": [
35 | {
36 | "nInputs": 1,
37 | "values": [
38 | "9223370907444564659",
39 | "3160103168159837358",
40 | "4556369264005083601",
41 | "25328597902566070",
42 | "1297268674017481603",
43 | "42248069444593420",
44 | "120056352688773499",
45 | "2505355624020802",
46 | "13635550101204825",
47 | "851857802305756",
48 | "4351741902257260",
49 | "614839285416132",
50 | "33443274990321",
51 | "2701860068338",
52 | "402411850209",
53 | "888964115465"
54 | ],
55 | "assetBlinders": [
56 | "686269065748c5a857c2fb4f76b28a283ebfe20c8ebd62f14eebf9cb109f0235",
57 | "12dff30d9161b951c3131eea628b4896534cec1fd561c15fd182e4db6c7656aa",
58 | "06e561259200ae50e1c2db18dc9b9979f88e0c0968aee8c91375a9f1d703a661",
59 | "4dc60247d300a857aa1edab32008b751e9f3c1be9c703e5618ba46c81bb25e2f",
60 | "4be849a18c5c794b37b2a037667ee6d018dca81cb2203937f0ab308eae56b280",
61 | "f3dd0b3b5b71529d6f802b117ac607c7cb4c1ba3710c88144b8ee98ba97a199d",
62 | "4340c5406fda91e9c1702dd877e0793f6ced41aaeaeaee54df7ace591d47f89d",
63 | "953d75c7dc02537a0644acc17eb47202f5098b0fd353d6071c650f78856fb165",
64 | "a93f37c1ed43772129e3fe502c67189e65db7a2f1e4b03cf0b17043f7ed3c784",
65 | "e44a7673bfeb34db61bbea9b8878dc2b29f6a44b04d691aeab3a3fdc355b7373",
66 | "f80381f807fbe4ba5dddcf985a5724d1ff8f2acb9589062611994aa773867d92",
67 | "95905f2391f19f87e922f94e8d42072e440d04e65b328b8a9e197450bc0247e3",
68 | "26e4d4150510aed890b0d1ef8170c0e360edf7e9a2912357590dc1773f34d0ff",
69 | "c5e693fa2b211849846b9f399253d80de9cee299207396b09f239375b6d5e6e7",
70 | "a89a92f3f06c9cb472f0159fe3379ac2681288376289b53f7307f16c955835fa",
71 | "7947cae8c870d7ac8bacf0ca0de26c9f6f0ed45acd1ad1ea83308775356538d9"
72 | ],
73 | "valueBlinders": [
74 | "455f6548abd2e95f5bf08c9051a7ee21cadac1df7df15f49a03363cd93da2a59",
75 | "3e1be9d94a83ae9c3c48a2b69b2a9df09e77185f213cb68dfd88c899ab9a4bdd",
76 | "d1f0b640aa3b5596d0ea8d18f1d0aaf1b5b1a2f230338b3b6cd5eab2c8eafe10",
77 | "1e619bb4b858e1a0dd2df2e5a993a896734d3f50b1db4a7afe754b3b17c16e7a",
78 | "0e4ecb61e53c7f23439795aaaa7d87d4b13d8597d0b611c406e501258308879c",
79 | "ec2cc90c34a67f7e829eb7bf314b637a621284a738d4a607013bebad0825a43c",
80 | "1fda366d7efb676f72567a975ec19ba9d83ba393dd91959f3db64dcff298d520",
81 | "ce636264c43656c290bf8aba799215960147911533442aae145bd2d2a03f66ab",
82 | "1fad1df18b5e6eeb2c1437636a3659da429d860e86e2c592aefcb9114f590db2",
83 | "8736e1dab7df51f7e894070af49feba2696afdcdacda938c881d043717fc6af0",
84 | "450f4ab37768b053c1ec850542add7bcb2c4f6053488678e7f9605153dbd8efb",
85 | "7e1d3514bb4df48a4bc42e925f3f0cc568d2a9d5bb30432a0625e02bfb6d455f",
86 | "d8ac5c36d74bfff54e4eb6fbc01dfb06c879eaa5f2b34a3eee2e0fc106225769",
87 | "7d6eadeacc65f3bfa6a24f2a2a28292edfa800786d3f1b7f600231417772c659",
88 | "4ca016bb7f7a15d8c0e1825fb20b76f59750555caf61da77573c028cd3ef62e7"
89 | ],
90 | "expected": "e1a140ae971b82910bec2d98a712f757a27d079ce87f1c55a2a0c36f5af27922"
91 | },
92 | {
93 | "nInputs": 0,
94 | "values": ["1402118166"],
95 | "valueBlinders": [],
96 | "assetBlinders": ["2a4573f14ccfb1175c4a1fbcb541ef124cabbab9dce98a783fa50b300ed2d5dd"],
97 | "expected": "9a0c1464053983bf75679461de15546afc7ab51e30301cfce50960829fee4565"
98 | },
99 | {
100 | "nInputs": 0,
101 | "values": ["3638801394"],
102 | "valueBlinders": [],
103 | "assetBlinders": ["56e33a1674c46b1951bb01131a2adfa3165ca7841fcb0b2e540e389766bfb5cd"],
104 | "expected": "87261e97f306cc30e9743fef0312e5ed3107b4c635e9601bc7f0c832f9268689"
105 | },
106 | {
107 | "nInputs": 0,
108 | "values": ["2211154800"],
109 | "valueBlinders": [],
110 | "assetBlinders": ["2c114e642f7336e6e4c99f67143710f8e8d0e790f05adb2175fe19afd7d0200f"],
111 | "expected": "e523b8f2fe9e044f4828159350596a4000fbcf12dfabfcd800ded564abf8c748"
112 | },
113 | {
114 | "nInputs": 0,
115 | "values": ["2438659112"],
116 | "valueBlinders": [],
117 | "assetBlinders": ["906bf458c1023e3af9c18a3284983615cc3ed7d7f529bff7a274a650d25d3ba2"],
118 | "expected": "fe14541a5a31cb9d42dbd96fae6c19695309c3431fde5289f8113ddcf9aeb34c"
119 | },
120 | {
121 | "nInputs": 0,
122 | "values": ["633090570"],
123 | "valueBlinders": [],
124 | "assetBlinders": ["78d892719df56ff76c6ea36f1e8e1eef9a96dd02ba6ca599f4cf6084b7338ab6"],
125 | "expected": "03ab9d48e82bae4912bceed99c77c6ecd4c1cd4f8f960512684e042d991d36f9"
126 | },
127 | {
128 | "nInputs": 0,
129 | "values": ["405385747"],
130 | "valueBlinders": [],
131 | "assetBlinders": ["c3446130478fc5e882712eba6eb17769b413a214689ba2e4f337366bb300cb16"],
132 | "expected": "dee847143a412489a8df899443913284213c1f5aa2a4bde73d4de528e16fb683"
133 | }
134 | ]
135 | }
--------------------------------------------------------------------------------
/src/lib/surjectionproof.ts:
--------------------------------------------------------------------------------
1 | import { CModule } from './cmodule';
2 | import { Secp256k1ZKP } from './interface';
3 | import Memory from './memory';
4 |
5 | function initialize(
6 | cModule: CModule
7 | ): Secp256k1ZKP['surjectionproof']['initialize'] {
8 | return function surjectionProofInitialize(
9 | inputTags: Uint8Array[],
10 | outputTag: Uint8Array,
11 | maxIterations: number,
12 | seed: Uint8Array
13 | ) {
14 | if (
15 | !inputTags ||
16 | !Array.isArray(inputTags) ||
17 | !inputTags.length ||
18 | !inputTags.every((t) => t.length === 32)
19 | )
20 | throw new TypeError(
21 | 'input tags must be a non-empty array of Uint8Arrays of 32 bytes'
22 | );
23 | if (
24 | !outputTag ||
25 | !(outputTag instanceof Uint8Array) ||
26 | outputTag.length !== 32
27 | )
28 | throw new TypeError('output tag must be a Uint8Array of 32 bytes');
29 | if (!seed || !(seed instanceof Uint8Array) || seed.length !== 32)
30 | throw new TypeError('seed must be a Uint8Array of 32 bytes');
31 |
32 | const memory = new Memory(cModule);
33 |
34 | const inputTagsToUse = inputTags.length > 3 ? 3 : inputTags.length;
35 | const output = memory.malloc(8258);
36 | const outputLength = memory.malloc(8);
37 | cModule.setValue(outputLength, 8258, 'i64');
38 | const inIndex = memory.malloc(4);
39 | cModule.setValue(inIndex, 0, 'i32');
40 | const ret = cModule.ccall(
41 | 'surjectionproof_initialize',
42 | 'number',
43 | [
44 | 'number',
45 | 'number',
46 | 'number',
47 | 'number',
48 | 'number',
49 | 'number',
50 | 'number',
51 | 'number',
52 | 'number',
53 | ],
54 | [
55 | output,
56 | outputLength,
57 | inIndex,
58 | memory.charStarArray(inputTags),
59 | inputTags.length,
60 | inputTagsToUse,
61 | memory.charStar(outputTag),
62 | maxIterations,
63 | memory.charStar(seed),
64 | ]
65 | );
66 | if (ret > 0) {
67 | const proof = new Uint8Array(
68 | cModule.HEAPU8.subarray(
69 | output,
70 | output + cModule.getValue(outputLength, 'i64')
71 | )
72 | );
73 | const inputIndex = cModule.getValue(inIndex, 'i32');
74 | memory.free();
75 | return { proof, inputIndex };
76 | } else {
77 | memory.free();
78 | throw new Error('secp256k1_surjectionproof_initialize');
79 | }
80 | };
81 | }
82 |
83 | function generate(
84 | cModule: CModule
85 | ): Secp256k1ZKP['surjectionproof']['generate'] {
86 | return function surjectionProofGenerate(
87 | proofData: Uint8Array,
88 | inputTags: Uint8Array[],
89 | outputTag: Uint8Array,
90 | inputIndex: number,
91 | inputBlindingKey: Uint8Array,
92 | outputBlindingKey: Uint8Array
93 | ) {
94 | if (!proofData || !(proofData instanceof Uint8Array))
95 | throw new TypeError('proof must be a non-empty Uint8Array');
96 | if (
97 | !inputTags ||
98 | !Array.isArray(inputTags) ||
99 | !inputTags.length ||
100 | !inputTags.every((t) => t.length === 33)
101 | )
102 | throw new TypeError(
103 | 'input tags must be a non-empty array of Uint8Arrays of 33 bytes'
104 | );
105 | if (
106 | !outputTag ||
107 | !(outputTag instanceof Uint8Array) ||
108 | outputTag.length !== 33
109 | )
110 | throw new TypeError('ouput tag must be a Uint8Array of 33 bytes');
111 | if (
112 | !inputBlindingKey ||
113 | !(inputBlindingKey instanceof Uint8Array) ||
114 | inputBlindingKey.length !== 32
115 | )
116 | throw new TypeError(
117 | 'input blinding key must be a Uint8Array of 32 bytes'
118 | );
119 | if (
120 | !outputBlindingKey ||
121 | !(outputBlindingKey instanceof Uint8Array) ||
122 | outputBlindingKey.length !== 32
123 | )
124 | throw new TypeError(
125 | 'output blinding key must be a Uint8Array of 32 bytes'
126 | );
127 | if (inputIndex < 0 || inputIndex > inputTags.length)
128 | throw new TypeError(
129 | `input index must be a number into range [0, ${inputTags.length}]`
130 | );
131 |
132 | const memory = new Memory(cModule);
133 |
134 | const output = memory.malloc(8258);
135 | const outputLength = memory.malloc(8);
136 |
137 | const ret = cModule.ccall(
138 | 'surjectionproof_generate',
139 | 'number',
140 | [
141 | 'number',
142 | 'number',
143 | 'number',
144 | 'number',
145 | 'number',
146 | 'number',
147 | 'number',
148 | 'number',
149 | 'number',
150 | 'number',
151 | ],
152 | [
153 | output,
154 | outputLength,
155 | memory.charStar(proofData),
156 | proofData.length,
157 | memory.charStarArray(inputTags),
158 | inputTags.length,
159 | memory.charStar(outputTag),
160 | inputIndex,
161 | memory.charStar(inputBlindingKey),
162 | memory.charStar(outputBlindingKey),
163 | ]
164 | );
165 | if (ret === 1) {
166 | const proof = new Uint8Array(
167 | cModule.HEAPU8.subarray(
168 | output,
169 | output + cModule.getValue(outputLength, 'i64')
170 | )
171 | );
172 | memory.free();
173 | return proof;
174 | } else {
175 | memory.free();
176 | throw new Error('secp256k1_surjectionproof_generate');
177 | }
178 | };
179 | }
180 |
181 | function verify(cModule: CModule): Secp256k1ZKP['surjectionproof']['verify'] {
182 | return function surjectionProofVerify(
183 | proof: Uint8Array,
184 | inputTags: Uint8Array[],
185 | outputTag: Uint8Array
186 | ) {
187 | if (!proof || !(proof instanceof Uint8Array) || !proof.length)
188 | throw new TypeError('proof must be a non-empty Uint8Array');
189 | if (
190 | !inputTags ||
191 | !Array.isArray(inputTags) ||
192 | !inputTags.length ||
193 | !inputTags.every((t) => t.length === 33)
194 | )
195 | throw new TypeError(
196 | 'input tags must be a non-empty array of Uint8Arrays of 33 bytes'
197 | );
198 | if (
199 | !outputTag ||
200 | !(outputTag instanceof Uint8Array) ||
201 | outputTag.length !== 33
202 | )
203 | throw new TypeError('ouput tag must be a Uint8Array of 33 bytes');
204 |
205 | const memory = new Memory(cModule);
206 |
207 | const ret = cModule.ccall(
208 | 'surjectionproof_verify',
209 | 'number',
210 | ['number', 'number', 'number', 'number', 'number'],
211 | [
212 | memory.charStar(proof),
213 | proof.length,
214 | memory.charStarArray(inputTags),
215 | inputTags.length,
216 | memory.charStar(outputTag),
217 | ]
218 | );
219 | memory.free();
220 | return ret === 1;
221 | };
222 | }
223 |
224 | export function surjectionproof(
225 | cModule: CModule
226 | ): Secp256k1ZKP['surjectionproof'] {
227 | return {
228 | initialize: initialize(cModule),
229 | generate: generate(cModule),
230 | verify: verify(cModule),
231 | };
232 | }
233 |
--------------------------------------------------------------------------------
/src/test/musig.spec.ts:
--------------------------------------------------------------------------------
1 | import { randomBytes } from 'crypto';
2 |
3 | import anyTest, { TestInterface } from 'ava';
4 | import ECPairFactory, { ECPairAPI } from 'ecpair';
5 |
6 | import { loadSecp256k1ZKP } from '../lib/cmodule';
7 | import { ecc } from '../lib/ecc';
8 | import { Secp256k1ZKP } from '../lib/interface';
9 | import { musig } from '../lib/musig';
10 |
11 | import fixtures from './fixtures/musig.json';
12 |
13 | const fromHex = (hex: string) => Buffer.from(hex, 'hex');
14 | const uintToString = (arr: Uint8Array) => Buffer.from(arr).toString('hex');
15 |
16 | const test = anyTest as TestInterface<
17 | Secp256k1ZKP['musig'] & { ecc: Secp256k1ZKP['ecc']; ec: ECPairAPI }
18 | >;
19 |
20 | test.before(async (t) => {
21 | const cModule = await loadSecp256k1ZKP();
22 | const eccModule = ecc(cModule);
23 | t.context = {
24 | ...musig(cModule),
25 | ecc: eccModule,
26 | ec: ECPairFactory(eccModule),
27 | };
28 | });
29 |
30 | test('pubkeyAgg', (t) => {
31 | const { pubkeyAgg } = t.context;
32 |
33 | fixtures.musigPubkeyAgg.forEach((f) => {
34 | const publicKeys = f.publicKeys.map((key) => fromHex(key));
35 | const res = pubkeyAgg(publicKeys);
36 |
37 | t.is(res.aggPubkey.length, 32);
38 | t.is(uintToString(res.aggPubkey), f.aggregatedPubkey);
39 | t.is(res.keyaggCache.length, 197);
40 | t.is(uintToString(res.keyaggCache), f.keyaggCache);
41 | });
42 | });
43 |
44 | test('nonceGen', (t) => {
45 | const { nonceGen } = t.context;
46 |
47 | fixtures.musigNonceGen.forEach((f) => {
48 | const pubKey = fromHex(f.publicKey);
49 | const session = fromHex(f.sessionId);
50 | const nonces = nonceGen(session, pubKey);
51 |
52 | t.is(nonces.secNonce.length, 132);
53 | t.is(uintToString(nonces.secNonce), f.secnonce);
54 | t.is(nonces.pubNonce.length, 66);
55 | t.is(uintToString(nonces.pubNonce), f.pubnonce);
56 | });
57 | });
58 |
59 | test('nonceAgg', (t) => {
60 | const { nonceAgg } = t.context;
61 |
62 | fixtures.musigNonceAgg.forEach((f) => {
63 | const nonces = f.pubnonces.map((nonce) => fromHex(nonce));
64 | const aggNonce = nonceAgg(nonces);
65 |
66 | t.is(aggNonce.length, 66);
67 | t.is(uintToString(aggNonce), f.aggnonce);
68 | });
69 | });
70 |
71 | test('nonceProcess', (t) => {
72 | const { nonceProcess } = t.context;
73 |
74 | fixtures.musigNonceProcess.forEach((f) => {
75 | const session = nonceProcess(
76 | fromHex(f.aggnonce),
77 | fromHex(f.message),
78 | fromHex(f.keyaggCache)
79 | );
80 |
81 | t.is(session.length, 133);
82 | t.is(uintToString(session), f.session);
83 | });
84 | });
85 |
86 | test('partialSign', (t) => {
87 | const musig = t.context;
88 |
89 | fixtures.musigPartialSign.forEach((f) => {
90 | const publicKeys = f.publicKeys.map(fromHex);
91 | const signer = musig.ec.fromPrivateKey(fromHex(f.privateKey));
92 | publicKeys[f.index] = signer.publicKey;
93 |
94 | const pubNonces: Uint8Array[] = f.pubnonces.map(fromHex);
95 | const signerNonces = musig.nonceGen(fromHex(f.sessionId), signer.publicKey);
96 | pubNonces[f.index] = signerNonces.pubNonce;
97 |
98 | const pubkeyAgg = musig.pubkeyAgg(publicKeys);
99 | const nonceAgg = musig.nonceAgg(pubNonces);
100 | const session = musig.nonceProcess(
101 | nonceAgg,
102 | fromHex(f.msg),
103 | pubkeyAgg.keyaggCache
104 | );
105 |
106 | const partialSig = musig.partialSign(
107 | signerNonces.secNonce,
108 | fromHex(f.privateKey),
109 | pubkeyAgg.keyaggCache,
110 | session
111 | );
112 | t.is(partialSig.length, 32);
113 | t.is(uintToString(partialSig), f.partialSig);
114 | });
115 | });
116 |
117 | test('partialVerify', (t) => {
118 | const musig = t.context;
119 |
120 | fixtures.musigPatialVerify.forEach((f) => {
121 | const publicKeys = f.publicKeys.map(fromHex);
122 | const pubkeyAgg = musig.pubkeyAgg(publicKeys);
123 |
124 | const pubNonces = f.pubnonces.map(fromHex);
125 | const nonceAgg = musig.nonceAgg(pubNonces);
126 |
127 | const session = musig.nonceProcess(
128 | nonceAgg,
129 | fromHex(f.msg),
130 | pubkeyAgg.keyaggCache
131 | );
132 |
133 | t.is(
134 | musig.partialVerify(
135 | fromHex(f.partialSig),
136 | pubNonces[f.index],
137 | publicKeys[f.index],
138 | pubkeyAgg.keyaggCache,
139 | session
140 | ),
141 | f.result
142 | );
143 | });
144 | });
145 |
146 | test('partialSigAgg', (t) => {
147 | const musig = t.context;
148 |
149 | fixtures.musigPartialSigAgg.forEach((f) => {
150 | const pubkeyAgg = musig.pubkeyAgg(f.publicKeys.map(fromHex));
151 |
152 | const nonceAgg = musig.nonceAgg(f.pubnonces.map(fromHex));
153 | t.is(uintToString(nonceAgg), f.aggnonce);
154 |
155 | const msg = fromHex(f.msg);
156 | const session = musig.nonceProcess(nonceAgg, msg, pubkeyAgg.keyaggCache);
157 |
158 | const partialSigs = f.partialSigs.map(fromHex);
159 | const aggregated = musig.partialSigAgg(session, partialSigs);
160 |
161 | t.is(aggregated.length, 64);
162 | t.is(uintToString(aggregated), f.aggregatedSignature);
163 | t.true(musig.ecc.verifySchnorr(msg, pubkeyAgg.aggPubkey, aggregated));
164 | });
165 | });
166 |
167 | test('pubkeyXonlyTweakAdd', (t) => {
168 | const { pubkeyXonlyTweakAdd } = t.context;
169 |
170 | fixtures.musigPubkeyXonlyTweakAdd.forEach((f) => {
171 | const tweaked = pubkeyXonlyTweakAdd(
172 | fromHex(f.keyaggCache),
173 | fromHex(f.tweak),
174 | f.compress
175 | );
176 |
177 | t.is(tweaked.pubkey.length, f.tweakedLength);
178 | t.is(uintToString(tweaked.pubkey), f.tweaked);
179 | });
180 | });
181 |
182 | test('full example', (t) => {
183 | const musig = t.context;
184 |
185 | const privateKeys = fixtures.fullExample.privateKeys.map(fromHex);
186 | const publicKeys = privateKeys.map(
187 | (key) => musig.ec.fromPrivateKey(key).publicKey
188 | );
189 | t.is(publicKeys.length, privateKeys.length);
190 |
191 | const pubkeyAgg = musig.pubkeyAgg(publicKeys);
192 |
193 | const nonces = publicKeys.map((publicKey) =>
194 | musig.nonceGen(randomBytes(32), publicKey)
195 | );
196 | const nonceAgg = musig.nonceAgg(nonces.map((nonce) => nonce.pubNonce));
197 |
198 | const message = randomBytes(32);
199 | const session = musig.nonceProcess(nonceAgg, message, pubkeyAgg.keyaggCache);
200 |
201 | const partialSigs = privateKeys.map((privateKey, i) =>
202 | musig.partialSign(
203 | nonces[i].secNonce,
204 | privateKey,
205 | pubkeyAgg.keyaggCache,
206 | session
207 | )
208 | );
209 |
210 | // Verify each partial signature individually, to make sure they are fine on their own
211 | partialSigs.forEach((sig, i) =>
212 | t.true(
213 | musig.partialVerify(
214 | sig,
215 | nonces[i].pubNonce,
216 | publicKeys[i],
217 | pubkeyAgg.keyaggCache,
218 | session
219 | )
220 | )
221 | );
222 |
223 | // Combine the partial signatures into one and verify it
224 | const sig = musig.partialSigAgg(session, partialSigs);
225 | t.true(musig.ecc.verifySchnorr(message, pubkeyAgg.aggPubkey, sig));
226 | });
227 |
228 | test('full example tweaked', (t) => {
229 | const musig = t.context;
230 |
231 | const privateKeys = fixtures.fullExample.privateKeys.map((key) =>
232 | fromHex(key)
233 | );
234 | const publicKeys = privateKeys.map(
235 | (key) => musig.ec.fromPrivateKey(key).publicKey
236 | );
237 | t.is(publicKeys.length, privateKeys.length);
238 |
239 | const pubkeyAgg = musig.pubkeyAgg(publicKeys);
240 | const tweak = musig.pubkeyXonlyTweakAdd(
241 | pubkeyAgg.keyaggCache,
242 | randomBytes(32),
243 | true
244 | );
245 |
246 | const nonces = publicKeys.map((publicKey) =>
247 | musig.nonceGen(randomBytes(32), publicKey)
248 | );
249 | const nonceAgg = musig.nonceAgg(nonces.map((nonce) => nonce.pubNonce));
250 |
251 | const message = randomBytes(32);
252 | const session = musig.nonceProcess(nonceAgg, message, tweak.keyaggCache);
253 |
254 | const partialSigs = privateKeys.map((privateKey, i) =>
255 | musig.partialSign(
256 | nonces[i].secNonce,
257 | privateKey,
258 | tweak.keyaggCache,
259 | session
260 | )
261 | );
262 |
263 | // Verify each partial signature individually, to make sure they are fine on their own
264 | partialSigs.forEach((sig, i) =>
265 | t.true(
266 | musig.partialVerify(
267 | sig,
268 | nonces[i].pubNonce,
269 | publicKeys[i],
270 | tweak.keyaggCache,
271 | session
272 | )
273 | )
274 | );
275 |
276 | // Combine the partial signatures into one and verify it
277 | const sig = musig.partialSigAgg(session, partialSigs);
278 | t.true(musig.ecc.verifySchnorr(message, tweak.pubkey.slice(1), sig));
279 | });
280 |
--------------------------------------------------------------------------------
/src/test/ecc.spec.ts:
--------------------------------------------------------------------------------
1 | import anyTest, { TestInterface } from 'ava';
2 |
3 | import { loadSecp256k1ZKP } from '../lib/cmodule';
4 | import { ecc } from '../lib/ecc';
5 | import { Secp256k1ZKP } from '../lib/interface';
6 |
7 | import fixtures from './fixtures/ecc.json';
8 |
9 | const fromHex = (hex: string) => Buffer.from(hex, 'hex');
10 | const toHex = (buf: Uint8Array) => Buffer.from(buf).toString('hex');
11 |
12 | const test = anyTest as TestInterface;
13 |
14 | test.before(async (t) => {
15 | const cModule = await loadSecp256k1ZKP();
16 | t.context = ecc(cModule);
17 | });
18 |
19 | test('privateNegate', (t) => {
20 | const { privateNegate } = t.context;
21 |
22 | fixtures.privateNegate.forEach((f) => {
23 | const key = fromHex(f.key);
24 | t.is(toHex(privateNegate(key)), f.expected);
25 | });
26 | });
27 |
28 | test('privateAdd', (t) => {
29 | const { privateAdd } = t.context;
30 |
31 | fixtures.privateAdd.forEach((f) => {
32 | const key = fromHex(f.key);
33 | const tweak = fromHex(f.tweak);
34 | const result = privateAdd(key, tweak);
35 | t.is(result ? toHex(result) : result, f.expected);
36 | });
37 | });
38 |
39 | test('privateSub', (t) => {
40 | const { privateSub } = t.context;
41 |
42 | fixtures.privateSub.valid.forEach((f) => {
43 | const key = fromHex(f.key);
44 | const tweak = fromHex(f.tweak);
45 | const result = privateSub(key, tweak);
46 | if (f.expected === null) {
47 | t.is(result, null);
48 | return;
49 | }
50 |
51 | if (result === null) {
52 | t.fail();
53 | return;
54 | }
55 |
56 | t.is(toHex(result), f.expected);
57 | });
58 | fixtures.privateSub.invalid.forEach((f) => {
59 | const key = fromHex(f.key);
60 | const tweak = fromHex(f.tweak);
61 | t.is(privateSub(key, tweak), null);
62 | });
63 | });
64 |
65 | test('privateMul', (t) => {
66 | const { privateMul } = t.context;
67 |
68 | fixtures.privateMul.forEach((f) => {
69 | const key = fromHex(f.key);
70 | const tweak = fromHex(f.tweak);
71 | t.is(toHex(privateMul(key, tweak)), f.expected);
72 | });
73 | });
74 |
75 | test('isPrivate', (t) => {
76 | const { isPrivate } = t.context;
77 |
78 | for (const f of fixtures.isPrivate) {
79 | const scalar = fromHex(f.scalar);
80 | t.is(isPrivate(scalar), f.expected);
81 | }
82 | });
83 |
84 | test('isPoint', (t) => {
85 | const { isPoint } = t.context;
86 |
87 | for (const f of fixtures.isPoint) {
88 | const point = fromHex(f.point);
89 | t.is(isPoint(point), f.expected);
90 | }
91 | });
92 |
93 | test('pointFromScalar', (t) => {
94 | const { pointFromScalar } = t.context;
95 |
96 | for (const f of fixtures.pointFromScalar) {
97 | const scalar = fromHex(f.scalar);
98 | if (f.expected === null) {
99 | t.is(pointFromScalar(scalar), null);
100 | continue;
101 | }
102 |
103 | const fromScalar = pointFromScalar(scalar);
104 | if (fromScalar === null) {
105 | t.fail();
106 | return;
107 | }
108 | const result = toHex(fromScalar);
109 | t.is(result, f.expected, `result: ${result} = expected: ${f.expected}`);
110 | }
111 | });
112 |
113 | test('pointCompress', (t) => {
114 | const { pointCompress } = t.context;
115 |
116 | for (const f of fixtures.pointCompress) {
117 | const point = Buffer.from(f.point, 'hex');
118 | const result = toHex(pointCompress(point));
119 | t.is(
120 | result,
121 | f.expected,
122 | `pointCompress(point): ${result} = expected: ${f.expected}`
123 | );
124 | }
125 | });
126 |
127 | test('xOnlyPointAddTweak', (t) => {
128 | const { xOnlyPointAddTweak } = t.context;
129 |
130 | fixtures.xOnlyPointAddTweak.forEach((f) => {
131 | const pubkey = fromHex(f.pubkey);
132 | const tweak = fromHex(f.tweak);
133 |
134 | const result = xOnlyPointAddTweak(pubkey, tweak);
135 | if (f.expected === null) {
136 | t.is(result, null);
137 | } else {
138 | if (result === null) {
139 | t.fail();
140 | return;
141 | }
142 | const { xOnlyPubkey, parity } = result;
143 | t.is(toHex(xOnlyPubkey), f.expected);
144 | t.is(parity, f.parity);
145 | }
146 | });
147 | });
148 |
149 | test('sign', (t) => {
150 | const { sign } = t.context;
151 |
152 | const buf1 = fromHex(
153 | '0000000000000000000000000000000000000000000000000000000000000000'
154 | );
155 | const buf2 = fromHex(
156 | '0000000000000000000000000000000000000000000000000000000000000001'
157 | );
158 | const buf3 = fromHex(
159 | '6e723d3fd94ed5d2b6bdd4f123364b0f3ca52af829988a63f8afe91d29db1c33'
160 | );
161 | const buf4 = fromHex(
162 | 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'
163 | );
164 | const buf5 = fromHex(
165 | 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
166 | );
167 |
168 | for (const f of fixtures.ecdsa.withoutExtraEntropy) {
169 | const scalar = fromHex(f.scalar);
170 | const message = fromHex(f.message);
171 | t.is(toHex(sign(message, scalar)), f.signature);
172 | }
173 |
174 | for (const f of fixtures.ecdsa.withExtraEntropy) {
175 | const scalar = fromHex(f.scalar);
176 | const message = fromHex(f.message);
177 | const expectedSig = fromHex(f.signature);
178 | const expectedExtraEntropy0 = fromHex(f.extraEntropy0);
179 | const expectedExtraEntropy1 = fromHex(f.extraEntropy1);
180 | const expectedExtraEntropyRand = fromHex(f.extraEntropyRand);
181 | const expectedExtraEntropyN = fromHex(f.extraEntropyN);
182 | const expectedExtraEntropyMax = fromHex(f.extraEntropyMax);
183 |
184 | const extraEntropyUndefined = sign(message, scalar);
185 | const extraEntropy0 = sign(message, scalar, buf1);
186 | const extraEntropy1 = sign(message, scalar, buf2);
187 | const extraEntropyRand = sign(message, scalar, buf3);
188 | const extraEntropyN = sign(message, scalar, buf4);
189 | const extraEntropyMax = sign(message, scalar, buf5);
190 |
191 | t.is(toHex(extraEntropyUndefined), toHex(expectedSig));
192 | t.is(toHex(extraEntropy0), toHex(expectedExtraEntropy0));
193 | t.is(toHex(extraEntropy1), toHex(expectedExtraEntropy1));
194 | t.is(toHex(extraEntropyRand), toHex(expectedExtraEntropyRand));
195 | t.is(toHex(extraEntropyN), toHex(expectedExtraEntropyN));
196 | t.is(toHex(extraEntropyMax), toHex(expectedExtraEntropyMax));
197 | }
198 | });
199 |
200 | test('verify', (t) => {
201 | const { verify } = t.context;
202 |
203 | for (const f of fixtures.ecdsa.withoutExtraEntropy) {
204 | const publicKey = fromHex(f.publicKey);
205 | const publicKeyUncompressed = fromHex(f.publicKeyUncompressed);
206 | const message = fromHex(f.message);
207 | const signature = fromHex(f.signature);
208 | const corruptedSignature = fromHex(f.corruptedSignature);
209 | t.is(verify(message, publicKey, signature), true);
210 | t.is(verify(message, publicKey, corruptedSignature), false);
211 | t.is(verify(message, publicKeyUncompressed, signature), true);
212 | t.is(verify(message, publicKeyUncompressed, corruptedSignature), false);
213 | }
214 | });
215 |
216 | test('signSchnorr', (t) => {
217 | const { signSchnorr } = t.context;
218 |
219 | for (const {
220 | message,
221 | scalar,
222 | extraEntropy,
223 | signature,
224 | exception,
225 | } of fixtures.schnorr) {
226 | if (!scalar) continue;
227 | if (exception) {
228 | t.throws(() =>
229 | signSchnorr(fromHex(message), fromHex(scalar), fromHex(extraEntropy))
230 | );
231 | continue;
232 | }
233 | t.is(
234 | toHex(
235 | signSchnorr(fromHex(message), fromHex(scalar), fromHex(extraEntropy))
236 | ),
237 | signature
238 | );
239 | }
240 | });
241 |
242 | test('verifySchnorr', (t) => {
243 | const { verifySchnorr } = t.context;
244 |
245 | for (const {
246 | message,
247 | publicKey,
248 | signature,
249 | exception,
250 | valid,
251 | } of fixtures.schnorr) {
252 | if (exception) continue; // do not verify invalid BIP340 test vectors
253 | t.is(
254 | verifySchnorr(fromHex(message), fromHex(publicKey), fromHex(signature)),
255 | valid
256 | );
257 | }
258 | });
259 |
260 | test('pointAddScalar', (t) => {
261 | const { pointAddScalar, pointCompress } = t.context;
262 |
263 | for (const f of fixtures.pointAddScalar) {
264 | const p = fromHex(f.P);
265 | const d = fromHex(f.d);
266 | const expected = f.expected;
267 | let description = `${f.P} + ${f.d} = ${f.expected ? f.expected : null}`;
268 | if (f.description) description += ` (${f.description})`;
269 | const result = pointAddScalar(p, d);
270 | t.is(result ? toHex(result) : null, expected, description);
271 | if (result !== null && expected !== null) {
272 | const compressed = pointAddScalar(p, d, true);
273 | if (compressed === null) {
274 | t.fail();
275 | return;
276 | }
277 | t.is(
278 | toHex(compressed),
279 | toHex(pointCompress(fromHex(expected), true)),
280 | description + ' (compressed)'
281 | );
282 |
283 | const uncompressed = pointAddScalar(p, d, false);
284 | if (uncompressed === null) {
285 | t.fail();
286 | return;
287 | }
288 | t.is(
289 | toHex(uncompressed),
290 | toHex(pointCompress(fromHex(expected), false)),
291 | description + ' (uncompressed)'
292 | );
293 | }
294 | }
295 | });
296 |
--------------------------------------------------------------------------------
/src/lib/rangeproof.ts:
--------------------------------------------------------------------------------
1 | import Long from 'long';
2 |
3 | import { CModule } from './cmodule';
4 | import { Secp256k1ZKP } from './interface';
5 | import Memory from './memory';
6 |
7 | function sign(cModule: CModule): Secp256k1ZKP['rangeproof']['sign'] {
8 | return function rangeProofSign(
9 | value: string,
10 | valueCommitment: Uint8Array,
11 | assetCommitment: Uint8Array,
12 | valueBlinder: Uint8Array,
13 | nonce: Uint8Array,
14 | minValue = '0',
15 | base10Exp = '0',
16 | minBits = '0',
17 | message = new Uint8Array(),
18 | extraCommitment = new Uint8Array()
19 | ) {
20 | if (
21 | !valueCommitment ||
22 | !(valueCommitment instanceof Uint8Array) ||
23 | !valueCommitment.length
24 | )
25 | throw new TypeError('value commitment must be a Uint8Array of 33 bytes');
26 | if (
27 | !assetCommitment ||
28 | !(assetCommitment instanceof Uint8Array) ||
29 | assetCommitment.length !== 33
30 | )
31 | throw new TypeError('asset commitment must be a Uint8Array of 33 bytes');
32 | if (
33 | !valueBlinder ||
34 | !(valueBlinder instanceof Uint8Array) ||
35 | valueBlinder.length !== 32
36 | )
37 | throw new TypeError('value blinder must be a Uint8Array of 32 bytes');
38 | if (!nonce || !(nonce instanceof Uint8Array) || !nonce.length)
39 | throw new TypeError('nonce must be a Uint8Array of 32 bytes');
40 | if (!(message instanceof Uint8Array))
41 | throw new TypeError('message must be a Uint8Array');
42 | if (!(extraCommitment instanceof Uint8Array))
43 | throw new TypeError('extra commitment must be a Uint8Array');
44 |
45 | const memory = new Memory(cModule);
46 |
47 | const proof = memory.malloc(5134);
48 | const plen = memory.malloc(8);
49 | cModule.setValue(plen, 5134, 'i64');
50 | const minValueLong = Long.fromString(minValue, true);
51 | const valueLong = Long.fromString(value, true);
52 | const exp = Number.parseInt(base10Exp, 10);
53 | const bits = Number.parseInt(minBits, 10);
54 |
55 | const ret = cModule.ccall(
56 | 'rangeproof_sign',
57 | 'number',
58 | [
59 | 'number',
60 | 'number',
61 | 'number',
62 | 'number',
63 | 'number',
64 | 'number',
65 | 'number',
66 | 'number',
67 | 'number',
68 | 'number',
69 | 'number',
70 | 'number',
71 | 'number',
72 | 'number',
73 | ],
74 | [
75 | proof,
76 | plen,
77 | memory.uint64Long(valueLong),
78 | memory.charStar(valueCommitment),
79 | memory.charStar(assetCommitment),
80 | memory.charStar(valueBlinder),
81 | memory.charStar(nonce),
82 | exp,
83 | bits,
84 | memory.uint64Long(minValueLong),
85 | memory.charStar(message),
86 | message.length,
87 | memory.charStar(extraCommitment),
88 | extraCommitment.length,
89 | ]
90 | );
91 | if (ret === 1) {
92 | const out = new Uint8Array(
93 | cModule.HEAPU8.subarray(proof, proof + cModule.getValue(plen, 'i64'))
94 | );
95 | memory.free();
96 | return out;
97 | } else {
98 | memory.free();
99 | throw new Error('secp256k1_rangeproof_sign');
100 | }
101 | };
102 | }
103 |
104 | function info(cModule: CModule): Secp256k1ZKP['rangeproof']['info'] {
105 | return function rangeProofInfo(proof: Uint8Array) {
106 | if (!proof || !(proof instanceof Uint8Array) || !proof.length)
107 | throw new TypeError('proof must be a non empty Uint8Array');
108 |
109 | const memory = new Memory(cModule);
110 |
111 | const exp = memory.malloc(4);
112 | const mantissa = memory.malloc(4);
113 | const min = memory.malloc(8);
114 | const max = memory.malloc(8);
115 | const ret = cModule.ccall(
116 | 'rangeproof_info',
117 | 'number',
118 | ['number', 'number', 'number', 'number', 'number', 'number'],
119 | [exp, mantissa, min, max, memory.charStar(proof), proof.length]
120 | );
121 |
122 | if (ret === 1) {
123 | const res = {
124 | exp: cModule.getValue(exp, 'i32').toString(),
125 | mantissa: cModule.getValue(mantissa, 'i32').toString(),
126 | minValue: memory.readUint64Long(min).toString(),
127 | maxValue: memory.readUint64Long(max).toString(),
128 | };
129 | memory.free();
130 | return res;
131 | } else {
132 | memory.free();
133 | throw new Error('secp256k1_rangeproof_info decode failed');
134 | }
135 | };
136 | }
137 |
138 | function verify(cModule: CModule): Secp256k1ZKP['rangeproof']['verify'] {
139 | return function rangeProofVerify(
140 | proof: Uint8Array,
141 | valueCommitment: Uint8Array,
142 | assetCommitment: Uint8Array,
143 | extraCommitment = new Uint8Array()
144 | ) {
145 | if (!proof || !(proof instanceof Uint8Array) || !proof.length)
146 | throw new TypeError('proof must be a non empty Uint8Array');
147 | if (
148 | !valueCommitment ||
149 | !(valueCommitment instanceof Uint8Array) ||
150 | valueCommitment.length !== 33
151 | )
152 | throw new TypeError('value commitment must be a Uint8Array of 33 bytes');
153 | if (
154 | !assetCommitment ||
155 | !(assetCommitment instanceof Uint8Array) ||
156 | assetCommitment.length !== 33
157 | )
158 | throw new TypeError('asset commitment must be a Uint8Array of 33 bytes');
159 | if (!extraCommitment || !(extraCommitment instanceof Uint8Array))
160 | throw new TypeError('extra commitment must be a Uint8Array');
161 |
162 | const memory = new Memory(cModule);
163 |
164 | const min = memory.malloc(8);
165 | const max = memory.malloc(8);
166 | const ret = cModule.ccall(
167 | 'rangeproof_verify',
168 | 'number',
169 | [
170 | 'number',
171 | 'number',
172 | 'number',
173 | 'number',
174 | 'number',
175 | 'number',
176 | 'number',
177 | 'number',
178 | ],
179 | [
180 | min,
181 | max,
182 | memory.charStar(proof),
183 | proof.length,
184 | memory.charStar(valueCommitment),
185 | memory.charStar(assetCommitment),
186 | memory.charStar(extraCommitment),
187 | extraCommitment.length,
188 | ]
189 | );
190 |
191 | memory.free();
192 | return ret === 1;
193 | };
194 | }
195 |
196 | function rewind(cModule: CModule) {
197 | return function rangeProofRewind(
198 | proof: Uint8Array,
199 | valueCommitment: Uint8Array,
200 | assetCommitment: Uint8Array,
201 | nonce: Uint8Array,
202 | extraCommitment = new Uint8Array()
203 | ) {
204 | if (!proof || !(proof instanceof Uint8Array) || !proof.length)
205 | throw new TypeError('proof must be a non-empty Uint8Array');
206 | if (
207 | !valueCommitment ||
208 | !(valueCommitment instanceof Uint8Array) ||
209 | valueCommitment.length !== 33
210 | )
211 | throw new TypeError('value commitment must be a Uint8Array of 33 bytes');
212 | if (
213 | !assetCommitment ||
214 | !(assetCommitment instanceof Uint8Array) ||
215 | assetCommitment.length !== 33
216 | )
217 | throw new TypeError('asset commitment must be a Uint8Array of 33 bytes');
218 | if (!nonce || !(nonce instanceof Uint8Array) || !nonce.length)
219 | throw new TypeError('nonce must be a non empty Uint8Array');
220 | if (!extraCommitment || !(extraCommitment instanceof Uint8Array))
221 | throw new TypeError('extra commitment must be a Uint8Array');
222 |
223 | const memory = new Memory(cModule);
224 |
225 | const blind = memory.malloc(32);
226 | const value = memory.malloc(8);
227 | const msg = memory.malloc(64);
228 | const msgLength = memory.malloc(8);
229 | const minValue = memory.malloc(8);
230 | const maxValue = memory.malloc(8);
231 | cModule.setValue(msgLength, 64, 'i64');
232 |
233 | const ret = cModule.ccall(
234 | 'rangeproof_rewind',
235 | 'number',
236 | [
237 | 'number',
238 | 'number',
239 | 'number',
240 | 'number',
241 | 'number',
242 | 'number',
243 | 'number',
244 | 'number',
245 | 'number',
246 | 'number',
247 | 'number',
248 | 'number',
249 | 'number',
250 | ],
251 | [
252 | blind,
253 | value,
254 | minValue,
255 | maxValue,
256 | msg,
257 | msgLength,
258 | memory.charStar(proof),
259 | proof.length,
260 | memory.charStar(valueCommitment),
261 | memory.charStar(assetCommitment),
262 | memory.charStar(nonce),
263 | memory.charStar(extraCommitment),
264 | extraCommitment.length,
265 | ]
266 | );
267 |
268 | if (ret === 1) {
269 | const blinder = new Uint8Array(
270 | cModule.HEAPU8.subarray(blind, blind + 32)
271 | );
272 | const message = new Uint8Array(
273 | cModule.HEAPU8.subarray(msg, msg + cModule.getValue(msgLength, 'i64'))
274 | );
275 | const out = {
276 | value: memory.readUint64Long(value).toString(),
277 | minValue: memory.readUint64Long(minValue).toString(),
278 | maxValue: memory.readUint64Long(maxValue).toString(),
279 | blinder,
280 | message,
281 | };
282 | memory.free();
283 | return out;
284 | } else {
285 | memory.free();
286 | throw new Error('secp256k1_rangeproof_rewind');
287 | }
288 | };
289 | }
290 |
291 | export function rangeproof(cModule: CModule): Secp256k1ZKP['rangeproof'] {
292 | return {
293 | info: info(cModule),
294 | rewind: rewind(cModule),
295 | sign: sign(cModule),
296 | verify: verify(cModule),
297 | };
298 | }
299 |
--------------------------------------------------------------------------------
/src/lib/musig.ts:
--------------------------------------------------------------------------------
1 | import { CModule } from './cmodule';
2 | import { Secp256k1ZKP } from './interface';
3 | import Memory from './memory';
4 |
5 | const keyaggCacheSize = 197;
6 | const nonceInternalSize = 132;
7 |
8 | function pubkeyAgg(cModule: CModule): Secp256k1ZKP['musig']['pubkeyAgg'] {
9 | return function pubkeyAgg(pubKeys: Array) {
10 | if (!pubKeys || !pubKeys.length) {
11 | throw TypeError('pubkeys must be an Array');
12 | }
13 |
14 | if (pubKeys.some((pubkey) => !(pubkey instanceof Uint8Array))) {
15 | throw TypeError('all elements of pubkeys must be Uint8Array');
16 | }
17 |
18 | if (pubKeys.some((pubkey) => pubkey.length !== pubKeys[0].length)) {
19 | throw TypeError('all elements of pubkeys must have same length');
20 | }
21 |
22 | const memory = new Memory(cModule);
23 | const aggPubkey = memory.malloc(32);
24 | const keyaggCache = memory.malloc(keyaggCacheSize);
25 |
26 | const ret = cModule.ccall(
27 | 'musig_pubkey_agg',
28 | 'number',
29 | ['number', 'number', 'number', 'number', 'number'],
30 | [
31 | aggPubkey,
32 | keyaggCache,
33 | memory.charStarArray(pubKeys),
34 | pubKeys.length,
35 | pubKeys[0].length,
36 | ]
37 | );
38 |
39 | if (ret !== 1) {
40 | memory.free();
41 | throw new Error('musig_pubkey_agg');
42 | }
43 |
44 | const res = {
45 | aggPubkey: memory.charStarToUint8(aggPubkey, 32),
46 | keyaggCache: memory.charStarToUint8(keyaggCache, keyaggCacheSize),
47 | };
48 | memory.free();
49 | return res;
50 | };
51 | }
52 |
53 | function nonceGen(cModule: CModule): Secp256k1ZKP['musig']['nonceGen'] {
54 | return function nonceGen(sessionId: Uint8Array, pubKey: Uint8Array) {
55 | if (!(sessionId instanceof Uint8Array)) {
56 | throw new TypeError('sessionId must be Uint8Array');
57 | }
58 |
59 | if (!(pubKey instanceof Uint8Array)) {
60 | throw new TypeError('pubkey must be Uint8Array');
61 | }
62 |
63 | const memory = new Memory(cModule);
64 | const secnonce = memory.malloc(nonceInternalSize);
65 | const pubnonce = memory.malloc(nonceInternalSize);
66 |
67 | const ret = cModule.ccall(
68 | 'musig_nonce_gen',
69 | 'number',
70 | ['number', 'number', 'number', 'number', 'number'],
71 | [
72 | secnonce,
73 | pubnonce,
74 | memory.charStar(sessionId),
75 | memory.charStar(pubKey),
76 | pubKey.length,
77 | ]
78 | );
79 |
80 | if (ret !== 1) {
81 | memory.free();
82 | throw new Error('musig_nonce_gen');
83 | }
84 |
85 | const res = {
86 | secNonce: memory.charStarToUint8(secnonce, nonceInternalSize),
87 | pubNonce: memory.charStarToUint8(pubnonce, 66),
88 | };
89 | memory.free();
90 | return res;
91 | };
92 | }
93 |
94 | function nonceAgg(cModule: CModule): Secp256k1ZKP['musig']['nonceAgg'] {
95 | return function nonceAgg(pubNonces: Array) {
96 | if (!pubNonces || !pubNonces.length) {
97 | throw TypeError('pubNonces must be an Array');
98 | }
99 |
100 | if (pubNonces.some((nonce) => !(nonce instanceof Uint8Array))) {
101 | throw TypeError('all elements of pubNonces must be Uint8Array');
102 | }
103 |
104 | const memory = new Memory(cModule);
105 | const aggNonce = memory.malloc(66);
106 |
107 | const ret = cModule.ccall(
108 | 'musig_nonce_agg',
109 | 'number',
110 | ['number', 'number', 'number'],
111 | [aggNonce, memory.charStarArray(pubNonces), pubNonces.length]
112 | );
113 |
114 | if (ret !== 1) {
115 | memory.free();
116 | throw new Error('musig_nonce_agg');
117 | }
118 |
119 | const res = memory.charStarToUint8(aggNonce, 66);
120 | memory.free();
121 | return res;
122 | };
123 | }
124 |
125 | function nonceProcess(cModule: CModule): Secp256k1ZKP['musig']['nonceProcess'] {
126 | return function nonceProcess(
127 | nonceAgg: Uint8Array,
128 | msg: Uint8Array,
129 | keyaggCache: Uint8Array
130 | ) {
131 | if (!(nonceAgg instanceof Uint8Array)) {
132 | throw new TypeError('nonceAgg must be Uint8Array');
133 | }
134 | if (!(msg instanceof Uint8Array)) {
135 | throw new TypeError('msg must be Uint8Array');
136 | }
137 | if (!(keyaggCache instanceof Uint8Array)) {
138 | throw new TypeError('keyaggCache must be Uint8Array');
139 | }
140 |
141 | const memory = new Memory(cModule);
142 | const session = memory.malloc(133);
143 |
144 | const ret = cModule.ccall(
145 | 'musig_nonce_process',
146 | 'number',
147 | ['number', 'number', 'number', 'number'],
148 | [
149 | session,
150 | memory.charStar(nonceAgg),
151 | memory.charStar(msg),
152 | memory.charStar(keyaggCache),
153 | ]
154 | );
155 |
156 | if (ret !== 1) {
157 | memory.free();
158 | throw new Error('musig_nonce_process');
159 | }
160 |
161 | const res = memory.charStarToUint8(session, 133);
162 | memory.free();
163 | return res;
164 | };
165 | }
166 |
167 | function partialSign(cModule: CModule): Secp256k1ZKP['musig']['partialSign'] {
168 | return function partialSign(
169 | secNonce: Uint8Array,
170 | secKey: Uint8Array,
171 | keyaggCache: Uint8Array,
172 | session: Uint8Array
173 | ) {
174 | if (!(secNonce instanceof Uint8Array)) {
175 | throw new TypeError('secNonce must be Uint8Array');
176 | }
177 | if (!(secKey instanceof Uint8Array)) {
178 | throw new TypeError('secKey must be Uint8Array');
179 | }
180 | if (!(keyaggCache instanceof Uint8Array)) {
181 | throw new TypeError('keyaggCache must be Uint8Array');
182 | }
183 | if (!(session instanceof Uint8Array)) {
184 | throw new TypeError('session must be Uint8Array');
185 | }
186 |
187 | const memory = new Memory(cModule);
188 | const partialSig = memory.malloc(32);
189 |
190 | const ret = cModule.ccall(
191 | 'musig_partial_sign',
192 | 'number',
193 | ['number', 'number', 'number', 'number', 'number'],
194 | [
195 | partialSig,
196 | memory.charStar(secNonce),
197 | memory.charStar(secKey),
198 | memory.charStar(keyaggCache),
199 | memory.charStar(session),
200 | ]
201 | );
202 |
203 | if (ret !== 1) {
204 | memory.free();
205 | throw new Error('musig_partial_sign');
206 | }
207 |
208 | const res = memory.charStarToUint8(partialSig, 32);
209 | memory.free();
210 | return res;
211 | };
212 | }
213 |
214 | function partialVerify(
215 | cModule: CModule
216 | ): Secp256k1ZKP['musig']['partialVerify'] {
217 | return function partialVerify(
218 | partialSig: Uint8Array,
219 | pubNonce: Uint8Array,
220 | pubKey: Uint8Array,
221 | keyaggCache: Uint8Array,
222 | session: Uint8Array
223 | ) {
224 | if (!(partialSig instanceof Uint8Array)) {
225 | throw new TypeError('partialSig must be Uint8Array');
226 | }
227 | if (!(pubNonce instanceof Uint8Array)) {
228 | throw new TypeError('pubNonce must be Uint8Array');
229 | }
230 | if (!(pubKey instanceof Uint8Array)) {
231 | throw new TypeError('pubKey must be Uint8Array');
232 | }
233 | if (!(keyaggCache instanceof Uint8Array)) {
234 | throw new TypeError('keyaggCache must be Uint8Array');
235 | }
236 | if (!(session instanceof Uint8Array)) {
237 | throw new TypeError('session must be Uint8Array');
238 | }
239 |
240 | const memory = new Memory(cModule);
241 | const ret = cModule.ccall(
242 | 'musig_partial_sig_verify',
243 | 'number',
244 | ['number', 'number', 'number', 'number', 'number', 'number'],
245 | [
246 | memory.charStar(partialSig),
247 | memory.charStar(pubNonce),
248 | memory.charStar(pubKey),
249 | pubKey.length,
250 | memory.charStar(keyaggCache),
251 | memory.charStar(session),
252 | ]
253 | );
254 |
255 | memory.free();
256 |
257 | // Return true when the signature was verified successfully
258 | return ret === 1;
259 | };
260 | }
261 |
262 | function partialSigAgg(
263 | cModule: CModule
264 | ): Secp256k1ZKP['musig']['partialSigAgg'] {
265 | return function partialSigAgg(
266 | session: Uint8Array,
267 | partialSigs: Array
268 | ) {
269 | if (!(session instanceof Uint8Array)) {
270 | throw new TypeError('session must be Uint8Array');
271 | }
272 | if (!partialSigs || !partialSigs.length) {
273 | throw new TypeError('partialSigs must be an Array');
274 | }
275 |
276 | if (partialSigs.some((sig) => !(sig instanceof Uint8Array))) {
277 | throw TypeError('all elements of partialSigs must be Uint8Array');
278 | }
279 |
280 | const memory = new Memory(cModule);
281 | const sig = memory.malloc(64);
282 |
283 | const ret = cModule.ccall(
284 | 'musig_partial_sig_agg',
285 | 'number',
286 | ['number', 'number', 'number', 'number'],
287 | [
288 | sig,
289 | memory.charStar(session),
290 | memory.charStarArray(partialSigs),
291 | partialSigs.length,
292 | ]
293 | );
294 |
295 | if (ret !== 1) {
296 | memory.free();
297 | throw new Error('musig_partial_sig_agg');
298 | }
299 |
300 | const res = memory.charStarToUint8(sig, 64);
301 | memory.free();
302 | return res;
303 | };
304 | }
305 |
306 | function pubkeyXonlyTweakAdd(
307 | cModule: CModule
308 | ): Secp256k1ZKP['musig']['pubkeyXonlyTweakAdd'] {
309 | return function pubkeyXonlyTweakAdd(
310 | keyaggCache: Uint8Array,
311 | tweak: Uint8Array,
312 | compress = true
313 | ) {
314 | if (!(keyaggCache instanceof Uint8Array)) {
315 | throw new TypeError('keyaggCache must be Uint8Array');
316 | }
317 | if (!(tweak instanceof Uint8Array)) {
318 | throw new TypeError('tweak must be Uint8Array');
319 | }
320 | if (typeof compress !== 'boolean') {
321 | throw new TypeError('compress must be boolean');
322 | }
323 |
324 | const memory = new Memory(cModule);
325 |
326 | const output = memory.malloc(65);
327 | const outputLen = memory.malloc(8);
328 | cModule.setValue(outputLen, 65, 'i64');
329 |
330 | const keyaggCacheTweaked = memory.charStar(keyaggCache);
331 |
332 | const ret = cModule.ccall(
333 | 'musig_pubkey_xonly_tweak_add',
334 | 'number',
335 | ['number', 'number', 'number', 'number', 'number'],
336 | [
337 | output,
338 | outputLen,
339 | compress ? 1 : 0,
340 | keyaggCacheTweaked,
341 | memory.charStar(tweak),
342 | ]
343 | );
344 |
345 | if (ret !== 1) {
346 | memory.free();
347 | throw new Error('musig_pubkey_xonly_tweak_add');
348 | }
349 |
350 | const pubkey = memory.charStarToUint8(
351 | output,
352 | cModule.getValue(outputLen, 'i64')
353 | );
354 | const keyaggCacheTweakedRes = memory.charStarToUint8(
355 | keyaggCacheTweaked,
356 | keyaggCacheSize
357 | );
358 |
359 | memory.free();
360 | return {
361 | pubkey,
362 | keyaggCache: keyaggCacheTweakedRes,
363 | };
364 | };
365 | }
366 |
367 | export function musig(cModule: CModule): Secp256k1ZKP['musig'] {
368 | return {
369 | pubkeyAgg: pubkeyAgg(cModule),
370 | nonceGen: nonceGen(cModule),
371 | nonceAgg: nonceAgg(cModule),
372 | nonceProcess: nonceProcess(cModule),
373 | partialSign: partialSign(cModule),
374 | partialVerify: partialVerify(cModule),
375 | partialSigAgg: partialSigAgg(cModule),
376 | pubkeyXonlyTweakAdd: pubkeyXonlyTweakAdd(cModule),
377 | };
378 | }
379 |
--------------------------------------------------------------------------------
/src/test/fixtures/musig.json:
--------------------------------------------------------------------------------
1 | {
2 | "musigPubkeyAgg": [
3 | {
4 | "publicKeys": [
5 | "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
6 | "03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
7 | "023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66"
8 | ],
9 | "aggregatedPubkey": "90539eede565f5d054f32cc0c220126889ed1e5d193baf15aef344fe59d4610c",
10 | "keyaggCache": "f4adbbdf0c61d459fe44f3ae15af3b195d1eed89681220c2c02cf354d0f565e5ed9e53903ca017f2bf635e49cd22820848f20d879e426c476c1a3cae3a9b55e6173443c759a62b507b0f2443d8cedea21daefe58be4123db263718365f1c672a7fd7f1df97d3a7b1127487d74d1a7a969fcaf85aa1f9e5bbe2da0ab97a1ab1936b641ed39d3ba89d01849c97649989f2a441f701be800aaa15d957e687d4f56479bc49b9000000000000000000000000000000000000000000000000000000000000000000"
11 | },
12 | {
13 | "publicKeys": [
14 | "023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66",
15 | "03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
16 | "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"
17 | ],
18 | "aggregatedPubkey": "6204de8b083426dc6eaf9502d27024d53fc826bf7d2012148a0575435df54b2b",
19 | "keyaggCache": "f4adbbdf2b4bf55d4375058a1412207dbf26c83fd52470d20295af6edc2634088bde0462dfd2228821001cef43fbff75d915c20b4c38ac84ca7f56ee53c7812ffdeef9f159a62b507b0f2443d8cedea21daefe58be4123db263718365f1c672a7fd7f1df97d3a7b1127487d74d1a7a969fcaf85aa1f9e5bbe2da0ab97a1ab1936b641ed37c4152183db84b9cd520d40d97dfd1f838cababdd9f49a30e222116fd07cf847000000000000000000000000000000000000000000000000000000000000000000"
20 | },
21 | {
22 | "publicKeys": [
23 | "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
24 | "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
25 | "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"
26 | ],
27 | "aggregatedPubkey": "b436e3bad62b8cd409969a224731c193d051162d8c5ae8b109306127da3aa935",
28 | "keyaggCache": "f4adbbdf35a93ada27613009b1e85a8c2d1651d093c13147229a9609d48c2bd6bae336b45205f568ccb0071ad833fb43a6ae63adfce4da9b454bf0f6470d1e197c331bf400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa4a50f3f0f2f53443963456187580b40c6ee07235a8aa101063a4a7fea61449000000000000000000000000000000000000000000000000000000000000000000"
29 | },
30 | {
31 | "publicKeys": [
32 | "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
33 | "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
34 | "03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
35 | "03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659"
36 | ],
37 | "aggregatedPubkey": "69bc22bfa5d106306e48a20679de1d7389386124d07571d0d872686028c26a3e",
38 | "keyaggCache": "f4adbbdf3e6ac228606872d8d07175d024613889731dde7906a2486e3006d1a5bf22bc6925b1d95bde8f74d0a44e938da9f50988809a5afa9c2e39300f5556da53fe817f59a62b507b0f2443d8cedea21daefe58be4123db263718365f1c672a7fd7f1df97d3a7b1127487d74d1a7a969fcaf85aa1f9e5bbe2da0ab97a1ab1936b641ed3333f8b6f05a23ae117e6170e64519c4575d5ea23d71d5d8a37a4451c4285740f000000000000000000000000000000000000000000000000000000000000000000"
39 | }
40 | ],
41 | "musigNonceGen": [
42 | {
43 | "sessionId": "0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f",
44 | "publicKey": "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
45 | "secnonce": "220edcf189bdd787d0284e5e4d5fc572e49e316bab7e21e3b1830de37dfe80156fa41a6d0b17ae8d024c53679699a6fd7944d9c4a366b514baf43088e0708b1023dd2897f936e0bc13f10186b0996f8345c831b529529df8854f344910c35892018a30f972e6b88475fdb96c1b23c23499a9006556f3372ae637e30f14e82d630f7b8f38",
46 | "pubnonce": "02c96e7cb1e8aa5dac64d872947914198f607d90ecde5200de52978ad5ded63c000299ec5117c2d29edee8a2092587c3909be694d5cff0667d6c02ea4059f7cd9786"
47 | }
48 | ],
49 | "musigNonceAgg": [
50 | {
51 | "pubnonces": [
52 | "032e5ecd340eb8a49dd95c26401c65dfc98d41f6c9b5b4c32d69b86da23874af3e02ea35cbc8c02d8242f22fcb7e27e9178b682d15088f919bd93decc4367c8a4c40",
53 | "037a02e3ceaf6b8a2da320d970024f5236f2ee04d1b92327e79ffc0bba037dad140318b6bae2d4f49ec293dc2283946bcad30c7bfdb81ace7a9055e0d02699401cf5",
54 | "03359cdf0defb7086d69e5aea5017250953921fe45414341a0f557d6d44858b711023109b647e2506c421699ab44e01d5df4a14c202113136d2556613e1e58ea45f0"
55 | ],
56 | "aggnonce": "03db0c4be460e7f97c415b19324e6e0c2df713e2c86be33f545572d1f3d24a22480356a73fa4cd9b5ccf66851981a322e5a3a99842efc7b4ddce37c8b9dfdfeb44f3"
57 | },
58 | {
59 | "pubnonces": [
60 | "020151c80f435648df67a22b749cd798ce54e0321d034b92b709b567d60a42e66603ba47fbc1834437b3212e89a84d8425e7bf12e0245d98262268ebdcb385d50641",
61 | "03ff406ffd8adb9cd29877e4985014f66a59f6cd01c0e88caa8e5f3166b1f676a60248c264cdd57d3c24d79990b0f865674eb62a0f9018277a95011b41bfc193b833"
62 | ],
63 | "aggnonce": "035fe1873b4f2967f52fea4a06ad5a8eccbe9d0fd73068012c894e2e87ccb5804b024725377345bde0e9c33af3c43c0a29a9249f2f2956fa8cfeb55c8573d0262dc8"
64 | },
65 | {
66 | "pubnonces": [
67 | "020151c80f435648df67a22b749cd798ce54e0321d034b92b709b567d60a42e6660279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
68 | "03ff406ffd8adb9cd29877e4985014f66a59f6cd01c0e88caa8e5f3166b1f676a60379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
69 | ],
70 | "aggnonce": "035fe1873b4f2967f52fea4a06ad5a8eccbe9d0fd73068012c894e2e87ccb5804b000000000000000000000000000000000000000000000000000000000000000000"
71 | }
72 | ],
73 | "musigNonceProcess": [
74 | {
75 | "aggnonce": "03db0c4be460e7f97c415b19324e6e0c2df713e2c86be33f545572d1f3d24a22480356a73fa4cd9b5ccf66851981a322e5a3a99842efc7b4ddce37c8b9dfdfeb44f3",
76 | "message": "b5ffefe7019df9425855ae58189e40811cb20df3f0c13e66d28231ddc83d23dc",
77 | "keyaggCache": "f4adbbdff8dce64c4175d4001e52cb29526c841b8d8b2bcfeed47cd4001f6d3564d4ca622bcea694fe603bc5b9cbb0d47c86f3f1c579bf5779e1bc0e1a6a8e9b38c3c77ebdaa2178ad0db31880dc326b1f8a6a383efd9a579962aac7008d8af738fa814dfaf634a378dd14e88d2709d00ec8c8c348c0668ab0de488c556ba6a6d22a9448000000000000000000000000000000000000000000000000000000000000000000",
78 | "session": "9dede91701c749f5d0b7895baa46956d4b902c2c8b6fcb0dc71dcbcde5eb00ef85d424efa06c3243e18a67c93c59dcb03d2938bd3799389130d06bc1095d8b7a5a664de81d6bce734a02c3948fee2067791a31d1fa6b4e8ac79d4617eecbce371d391db602f9c5d73c7ec7f390c88b50e25cc3e5c5c7ebb575598df79efdcf9245b902c889"
79 | }
80 | ],
81 | "musigPartialSign": [
82 | {
83 | "publicKeys": [
84 | "",
85 | "02d2dc6f5df7c56acf38c7fa0ae7a759ae30e19b37359dfde015872324c7ef6e05"
86 | ],
87 | "pubnonces": [
88 | "",
89 | "03e4f798da48a76eec1c9cc5ab7a880ffba201a5f064e627ec9cb0031d1d58fc5103e06180315c5a522b7ec7c08b69dcd721c313c940819296d0a7ab8e8795ac1f00"
90 | ],
91 | "msg": "599c67ea410d005b9da90817cf03ed3b1c868e4da4edf00a5880b0082c237869",
92 | "index": 0,
93 | "sessionId": "0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f",
94 | "privateKey": "b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfef",
95 | "partialSig": "ec918453ec6270ab087106815f77536c160927547a1af38f8b2f17a1da77fb31"
96 | }
97 | ],
98 | "musigPatialVerify": [
99 | {
100 | "publicKeys": [
101 | "03935f972da013f80ae011890fa89b67a27b7be6ccb24d3274d18b2d4067f261a9",
102 | "02d2dc6f5df7c56acf38c7fa0ae7a759ae30e19b37359dfde015872324c7ef6e05"
103 | ],
104 | "pubnonces": [
105 | "036e5ee6e28824029fea3e8a9ddd2c8483f5af98f7177c3af3cb6f47caf8d94ae902dba67e4a1f3680826172da15afb1a8ca85c7c5cc88900905c8dc8c328511b53e",
106 | "03e4f798da48a76eec1c9cc5ab7a880ffba201a5f064e627ec9cb0031d1d58fc5103e06180315c5a522b7ec7c08b69dcd721c313c940819296d0a7ab8e8795ac1f00"
107 | ],
108 | "msg": "599c67ea410d005b9da90817cf03ed3b1c868e4da4edf00a5880b0082c237869",
109 | "index": 0,
110 | "partialSig": "b15d2cd3c3d22b04dae438ce653f6b4ecf042f42cfded7c41b64aaf9b4af53fb",
111 | "result": true
112 | },
113 | {
114 | "publicKeys": [
115 | "03935f972da013f80ae011890fa89b67a27b7be6ccb24d3274d18b2d4067f261a9",
116 | "02d2dc6f5df7c56acf38c7fa0ae7a759ae30e19b37359dfde015872324c7ef6e05"
117 | ],
118 | "pubnonces": [
119 | "036e5ee6e28824029fea3e8a9ddd2c8483f5af98f7177c3af3cb6f47caf8d94ae902dba67e4a1f3680826172da15afb1a8ca85c7c5cc88900905c8dc8c328511b53e",
120 | "03e4f798da48a76eec1c9cc5ab7a880ffba201a5f064e627ec9cb0031d1d58fc5103e06180315c5a522b7ec7c08b69dcd721c313c940819296d0a7ab8e8795ac1f00"
121 | ],
122 | "msg": "599c67ea410d005b9da90817cf03ed3b1c868e4da4edf00a5880b0082c237869",
123 | "index": 1,
124 | "partialSig": "6193d6ac61b354e9105bbdc8937a3454a6d705b6d57322a5a472a02ce99fcb64",
125 | "result": true
126 | }
127 | ],
128 | "musigPartialSigAgg": [
129 | {
130 | "publicKeys": [
131 | "03935f972da013f80ae011890fa89b67a27b7be6ccb24d3274d18b2d4067f261a9",
132 | "02d2dc6f5df7c56acf38c7fa0ae7a759ae30e19b37359dfde015872324c7ef6e05"
133 | ],
134 | "pubnonces": [
135 | "036e5ee6e28824029fea3e8a9ddd2c8483f5af98f7177c3af3cb6f47caf8d94ae902dba67e4a1f3680826172da15afb1a8ca85c7c5cc88900905c8dc8c328511b53e",
136 | "03e4f798da48a76eec1c9cc5ab7a880ffba201a5f064e627ec9cb0031d1d58fc5103e06180315c5a522b7ec7c08b69dcd721c313c940819296d0a7ab8e8795ac1f00"
137 | ],
138 | "aggnonce": "0341432722c5cd0268d829c702cf0d1cbce57033eed201fd335191385227c3210c03d377f2d258b64aadc0e16f26462323d701d286046a2ea93365656afd9875982b",
139 | "msg": "599c67ea410d005b9da90817cf03ed3b1c868e4da4edf00a5880b0082c237869",
140 | "partialSigs": [
141 | "b15d2cd3c3d22b04dae438ce653f6b4ecf042f42cfded7c41b64aaf9b4af53fb",
142 | "6193d6ac61b354e9105bbdc8937a3454a6d705b6d57322a5a472a02ce99fcb64"
143 | ],
144 | "aggregatedSignature": "041da22223ce65c92c9a0d6c2cac828aaf1eee56304fec371ddf91ebb2b9ef0912f1038025857fedeb3ff696f8b99fa4bb2c5812f6095a2e0004ec99ce18de1e"
145 | },
146 | {
147 | "publicKeys": [
148 | "03935f972da013f80ae011890fa89b67a27b7be6ccb24d3274d18b2d4067f261a9",
149 | "03c7fb101d97ff930acd0c6760852ef64e69083de0b06ac6335724754bb4b0522c"
150 | ],
151 | "pubnonces": [
152 | "036e5ee6e28824029fea3e8a9ddd2c8483f5af98f7177c3af3cb6f47caf8d94ae902dba67e4a1f3680826172da15afb1a8ca85c7c5cc88900905c8dc8c328511b53e",
153 | "02c0068fd25523a31578b8077f24f78f5bd5f2422aff47c1fada0f36b3ceb6c7d202098a55d1736aa5fcc21cf0729cce852575c06c081125144763c2c4c4a05c09b6"
154 | ],
155 | "aggnonce": "0224afd36c902084058b51b5d36676bba4dc97c775873768e58822f87fe437d792028cb15929099eee2f5dae404cd39357591ba32e9af4e162b8d3e7cb5efe31cb20",
156 | "msg": "599c67ea410d005b9da90817cf03ed3b1c868e4da4edf00a5880b0082c237869",
157 | "partialSigs": [
158 | "9a87d3b79ec67228cb97878b76049b15dbd05b8158d17b5b9114d3c226887505",
159 | "66f82ea90923689b855d36c6b7e032fb9970301481b99e01cdb4d6ac7c347a15"
160 | ],
161 | "aggregatedSignature": "1069b67ec3d2f3c7c08291accb17a9c9b8f2819a52eb5df8726e17e7d6b52e9f01800260a7e9dac450f4be522de4ce12ba91aeaf2b4279219ef74be1d286add9"
162 | }
163 | ],
164 | "musigPubkeyXonlyTweakAdd": [
165 | {
166 | "keyaggCache": "f4adbbdf35a93ada27613009b1e85a8c2d1651d093c13147229a9609d48c2bd6bae336b45205f568ccb0071ad833fb43a6ae63adfce4da9b454bf0f6470d1e197c331bf400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa4a50f3f0f2f53443963456187580b40c6ee07235a8aa101063a4a7fea61449000000000000000000000000000000000000000000000000000000000000000000",
167 | "tweak": "6b21124e953955ce3c6907c2a503fb0b2b95714b93321d10882b48b43163fb14",
168 | "compress": true,
169 | "tweakedLength": 33,
170 | "tweaked": "02fff555c0fb2102acdff402bbb7b8c878306319a030480efb19252b784d2ff0a8"
171 | },
172 | {
173 | "keyaggCache": "f4adbbdf35a93ada27613009b1e85a8c2d1651d093c13147229a9609d48c2bd6bae336b45205f568ccb0071ad833fb43a6ae63adfce4da9b454bf0f6470d1e197c331bf400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa4a50f3f0f2f53443963456187580b40c6ee07235a8aa101063a4a7fea61449000000000000000000000000000000000000000000000000000000000000000000",
174 | "tweak": "6b21124e953955ce3c6907c2a503fb0b2b95714b93321d10882b48b43163fb14",
175 | "compress": false,
176 | "tweakedLength": 65,
177 | "tweaked": "04fff555c0fb2102acdff402bbb7b8c878306319a030480efb19252b784d2ff0a8921351ddba2cd9724e4555f0dedf7b6cf30ca0dfc20252387d56eaf66d670bca"
178 | }
179 | ],
180 | "fullExample": {
181 | "privateKeys": [
182 | "b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfef",
183 | "c90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b14e5c9",
184 | "0b432b2677937381aef05bb02a66ecd012773062cf3fa2549e44f58ed2401710"
185 | ]
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/src/lib/ecc.ts:
--------------------------------------------------------------------------------
1 | import { CModule } from './cmodule';
2 | import { Secp256k1ZKP } from './interface';
3 | import Memory from './memory';
4 |
5 | function privateNegate(cModule: CModule): Secp256k1ZKP['ecc']['privateNegate'] {
6 | return function (key: Uint8Array): Uint8Array {
7 | if (!key || !(key instanceof Uint8Array) || key.length !== 32) {
8 | throw new TypeError('key must be a non-empty Uint8Array of 32 bytes');
9 | }
10 | const memory = new Memory(cModule);
11 | const keyPtr = memory.charStar(key);
12 | const ret = cModule.ccall(
13 | 'ec_seckey_negate',
14 | 'number',
15 | ['number'],
16 | [keyPtr]
17 | );
18 |
19 | if (ret === 1) {
20 | const out = new Uint8Array(cModule.HEAPU8.subarray(keyPtr, keyPtr + 32));
21 | memory.free();
22 | return out;
23 | }
24 | memory.free();
25 | throw new Error('ec_seckey_negate');
26 | };
27 | }
28 |
29 | function privateAdd(cModule: CModule): Secp256k1ZKP['ecc']['privateAdd'] {
30 | return function (key: Uint8Array, tweak: Uint8Array): Uint8Array | null {
31 | if (!key || !(key instanceof Uint8Array) || key.length !== 32) {
32 | throw new TypeError('key must be a non-empty Uint8Array of 32 bytes');
33 | }
34 | if (!tweak || !(tweak instanceof Uint8Array) || tweak.length !== 32) {
35 | throw new TypeError('tweak must be a non-empty Uint8Array of 32 bytes');
36 | }
37 | const memory = new Memory(cModule);
38 |
39 | const keyPtr = memory.charStar(key);
40 | const ret = cModule.ccall(
41 | 'ec_seckey_tweak_add',
42 | 'number',
43 | ['number', 'number'],
44 | [keyPtr, memory.charStar(tweak)]
45 | );
46 |
47 | let out = null;
48 | if (ret === 1) {
49 | out = new Uint8Array(cModule.HEAPU8.subarray(keyPtr, keyPtr + 32));
50 | }
51 | memory.free();
52 | return out;
53 | };
54 | }
55 |
56 | function privateSub(cModule: CModule): Secp256k1ZKP['ecc']['privateSub'] {
57 | return function (key: Uint8Array, tweak: Uint8Array) {
58 | if (!key || !(key instanceof Uint8Array) || key.length !== 32) {
59 | throw new TypeError('key must be a non-empty Uint8Array of 32 bytes');
60 | }
61 | if (!tweak || !(tweak instanceof Uint8Array) || tweak.length !== 32) {
62 | throw new TypeError('tweak must be a non-empty Uint8Array of 32 bytes');
63 | }
64 | const memory = new Memory(cModule);
65 |
66 | const keyPtr = memory.charStar(key);
67 | const ret = cModule.ccall(
68 | 'ec_seckey_tweak_sub',
69 | 'number',
70 | ['number', 'number'],
71 | [keyPtr, memory.charStar(tweak)]
72 | );
73 |
74 | if (ret === 1) {
75 | const out = new Uint8Array(cModule.HEAPU8.subarray(keyPtr, keyPtr + 32));
76 | memory.free();
77 | return out;
78 | }
79 | memory.free();
80 | return null;
81 | };
82 | }
83 |
84 | function privateMul(cModule: CModule): Secp256k1ZKP['ecc']['privateMul'] {
85 | return function (key: Uint8Array, tweak: Uint8Array) {
86 | if (!key || !(key instanceof Uint8Array) || key.length !== 32) {
87 | throw new TypeError('key must be a non-empty Uint8Array of 32 bytes');
88 | }
89 | if (!tweak || !(tweak instanceof Uint8Array) || tweak.length !== 32) {
90 | throw new TypeError('tweak must be a non-empty Uint8Array of 32 bytes');
91 | }
92 | const memory = new Memory(cModule);
93 |
94 | const keyPtr = memory.charStar(key);
95 | const ret = cModule.ccall(
96 | 'ec_seckey_tweak_mul',
97 | 'number',
98 | ['number', 'number'],
99 | [keyPtr, memory.charStar(tweak)]
100 | );
101 |
102 | if (ret === 1) {
103 | const out = new Uint8Array(cModule.HEAPU8.subarray(keyPtr, keyPtr + 32));
104 | memory.free();
105 | return out;
106 | }
107 | memory.free();
108 | throw new Error('ec_seckey_tweak_mul');
109 | };
110 | }
111 |
112 | function isPoint(cModule: CModule): Secp256k1ZKP['ecc']['isPoint'] {
113 | return function (point: Uint8Array) {
114 | if (!point || !(point instanceof Uint8Array)) {
115 | throw new TypeError('point must be a Uint8Array');
116 | }
117 | const memory = new Memory(cModule);
118 |
119 | const pointPtr = memory.charStar(point);
120 | const res = cModule.ccall(
121 | 'ec_is_point',
122 | 'number',
123 | ['number', 'number'],
124 | [pointPtr, point.length]
125 | );
126 | memory.free();
127 | return res === 1;
128 | };
129 | }
130 |
131 | function pointCompress(cModule: CModule): Secp256k1ZKP['ecc']['pointCompress'] {
132 | return function (point: Uint8Array, compress = true) {
133 | if (!point || !(point instanceof Uint8Array)) {
134 | throw new TypeError('point must be a Uint8Array');
135 | }
136 | const memory = new Memory(cModule);
137 |
138 | const len = compress ? 33 : 65;
139 | const output = memory.malloc(len);
140 | const outputlen = memory.malloc(8);
141 | cModule.setValue(outputlen, len, 'i64');
142 |
143 | const ret = cModule.ccall(
144 | 'ec_point_compress',
145 | 'number',
146 | ['number', 'number', 'number', 'number', 'number'],
147 | [
148 | output,
149 | outputlen,
150 | memory.charStar(point),
151 | point.length,
152 | compress ? 1 : 0,
153 | ]
154 | );
155 |
156 | if (ret === 1) {
157 | const res = new Uint8Array(cModule.HEAPU8.subarray(output, output + len));
158 | memory.free();
159 | return res;
160 | }
161 | memory.free();
162 | throw new Error('point_compress');
163 | };
164 | }
165 |
166 | function isPrivate(cModule: CModule): Secp256k1ZKP['ecc']['isPrivate'] {
167 | return function (point: Uint8Array) {
168 | if (!point || !(point instanceof Uint8Array)) {
169 | throw new TypeError('point must be a Uint8Array');
170 | }
171 | const memory = new Memory(cModule);
172 |
173 | const dPtr = memory.charStar(point);
174 | const ret = cModule.ccall('ec_seckey_verify', 'number', ['number'], [dPtr]);
175 | memory.free();
176 | return ret === 1;
177 | };
178 | }
179 |
180 | function pointFromScalar(
181 | cModule: CModule
182 | ): Secp256k1ZKP['ecc']['pointFromScalar'] {
183 | return function (scalar: Uint8Array, compress = true) {
184 | if (!scalar || !(scalar instanceof Uint8Array)) {
185 | throw new TypeError('scalar must be a Uint8Array');
186 | }
187 | const memory = new Memory(cModule);
188 |
189 | const len = compress ? 33 : 65;
190 | const output = memory.malloc(len);
191 | const outputlen = memory.malloc(8);
192 | cModule.setValue(outputlen, len, 'i64');
193 |
194 | const ret = cModule.ccall(
195 | 'ec_point_from_scalar',
196 | 'number',
197 | ['number', 'number', 'number', 'number'],
198 | [output, outputlen, memory.charStar(scalar), compress ? 1 : 0]
199 | );
200 | if (ret === 1) {
201 | const res = new Uint8Array(cModule.HEAPU8.subarray(output, output + len));
202 | memory.free();
203 | return res;
204 | }
205 | memory.free();
206 | return null;
207 | };
208 | }
209 |
210 | function validateParity(n: number): n is 1 | 0 {
211 | return n === 1 || n === 0;
212 | }
213 |
214 | function xOnlyPointAddTweak(
215 | cModule: CModule
216 | ): Secp256k1ZKP['ecc']['xOnlyPointAddTweak'] {
217 | return function (point: Uint8Array, tweak: Uint8Array) {
218 | if (!point || !(point instanceof Uint8Array) || point.length !== 32) {
219 | throw new TypeError('point must be a Uint8Array of 32 bytes');
220 | }
221 | if (!tweak || !(tweak instanceof Uint8Array) || tweak.length !== 32) {
222 | throw new TypeError('tweak must be a Uint8Array of 32 bytes');
223 | }
224 | const memory = new Memory(cModule);
225 |
226 | const output = memory.malloc(32);
227 | const parityBit = memory.malloc(4);
228 | cModule.setValue(parityBit, 0, 'i32');
229 | const res = cModule.ccall(
230 | 'ec_x_only_point_tweak_add',
231 | 'number',
232 | ['number', 'number', 'number', 'number'],
233 | [output, parityBit, memory.charStar(point), memory.charStar(tweak)]
234 | );
235 | if (res === 1) {
236 | const xOnlyPubkey = new Uint8Array(
237 | cModule.HEAPU8.subarray(output, output + 32)
238 | );
239 | const parity = cModule.getValue(parityBit, 'i32');
240 | if (!validateParity(parity)) {
241 | throw new Error('parity is not valid');
242 | }
243 | memory.free();
244 | return { xOnlyPubkey, parity };
245 | }
246 | memory.free();
247 | return null;
248 | };
249 | }
250 |
251 | function signECDSA(cModule: CModule): Secp256k1ZKP['ecc']['sign'] {
252 | return function (
253 | message: Uint8Array,
254 | privateKey: Uint8Array,
255 | extraEntropy?: Uint8Array
256 | ) {
257 | if (!message || !(message instanceof Uint8Array)) {
258 | throw new TypeError('message must be a Uint8Array');
259 | }
260 | if (!privateKey || !(privateKey instanceof Uint8Array)) {
261 | throw new TypeError('privateKey must be a Uint8Array');
262 | }
263 | if (extraEntropy && !(extraEntropy instanceof Uint8Array)) {
264 | throw new TypeError('extraEntropy must be a Uint8Array');
265 | }
266 | const memory = new Memory(cModule);
267 |
268 | const output = memory.malloc(64);
269 | const hPtr = memory.charStar(message);
270 | const dPtr = memory.charStar(privateKey);
271 | const ret = cModule.ccall(
272 | 'ec_sign_ecdsa',
273 | 'number',
274 | ['number', 'number', 'number', 'number', 'number'],
275 | [
276 | output,
277 | dPtr,
278 | hPtr,
279 | extraEntropy ? 1 : 0,
280 | extraEntropy ? memory.charStar(extraEntropy) : 0,
281 | ]
282 | );
283 | if (ret === 1) {
284 | const res = new Uint8Array(cModule.HEAPU8.subarray(output, output + 64));
285 | memory.free();
286 | return res;
287 | }
288 | memory.free();
289 | throw new Error('sign_ecdsa');
290 | };
291 | }
292 |
293 | function verifyECDSA(cModule: CModule): Secp256k1ZKP['ecc']['verify'] {
294 | return function (
295 | message: Uint8Array,
296 | publicKey: Uint8Array,
297 | signature: Uint8Array,
298 | strict = false
299 | ) {
300 | if (!message || !(message instanceof Uint8Array)) {
301 | throw new TypeError('message must be a Uint8Array');
302 | }
303 | if (!publicKey || !(publicKey instanceof Uint8Array)) {
304 | throw new TypeError('publicKey must be a Uint8Array');
305 | }
306 | if (!signature || !(signature instanceof Uint8Array)) {
307 | throw new TypeError('signature must be a Uint8Array');
308 | }
309 | if (typeof strict !== 'boolean') {
310 | throw new TypeError('strict must be a boolean');
311 | }
312 | const memory = new Memory(cModule);
313 |
314 | const ret = cModule.ccall(
315 | 'ec_verify_ecdsa',
316 | 'number',
317 | ['number', 'number', 'number', 'number', 'number'],
318 | [
319 | memory.charStar(publicKey),
320 | publicKey.length,
321 | memory.charStar(message),
322 | memory.charStar(signature),
323 | strict ? 1 : 0,
324 | ]
325 | );
326 | memory.free();
327 | return ret === 1;
328 | };
329 | }
330 |
331 | function signSchnorr(cModule: CModule): Secp256k1ZKP['ecc']['signSchnorr'] {
332 | return function (
333 | message: Uint8Array,
334 | privateKey: Uint8Array,
335 | extraEntropy?: Uint8Array
336 | ) {
337 | if (!message || !(message instanceof Uint8Array)) {
338 | throw new TypeError('message must be a Uint8Array');
339 | }
340 | if (!privateKey || !(privateKey instanceof Uint8Array)) {
341 | throw new TypeError('privateKey must be a Uint8Array');
342 | }
343 | if (
344 | extraEntropy &&
345 | (!(extraEntropy instanceof Uint8Array) || extraEntropy.length !== 32)
346 | ) {
347 | throw new TypeError('extraEntropy must be a 32-byte Uint8Array');
348 | }
349 | const memory = new Memory(cModule);
350 |
351 | const output = memory.malloc(64);
352 | const ret = cModule.ccall(
353 | 'ec_sign_schnorr',
354 | 'number',
355 | ['number', 'number', 'number', 'number', 'number'],
356 | [
357 | output,
358 | memory.charStar(privateKey),
359 | memory.charStar(message),
360 | extraEntropy ? 1 : 0,
361 | extraEntropy ? memory.charStar(extraEntropy) : 0,
362 | ]
363 | );
364 | if (ret === 1) {
365 | const res = new Uint8Array(cModule.HEAPU8.subarray(output, output + 64));
366 | memory.free();
367 | return res;
368 | }
369 | memory.free();
370 | throw new Error('schnorr_sign');
371 | };
372 | }
373 |
374 | function verifySchnorr(cModule: CModule): Secp256k1ZKP['ecc']['verifySchnorr'] {
375 | return function (
376 | message: Uint8Array,
377 | publicKey: Uint8Array,
378 | signature: Uint8Array
379 | ) {
380 | if (!message || !(message instanceof Uint8Array)) {
381 | throw new TypeError('message must be a Uint8Array');
382 | }
383 | if (!publicKey || !(publicKey instanceof Uint8Array)) {
384 | throw new TypeError('publicKey must be a Uint8Array');
385 | }
386 | if (!signature || !(signature instanceof Uint8Array)) {
387 | throw new TypeError('signature must be a Uint8Array');
388 | }
389 | const memory = new Memory(cModule);
390 |
391 | const ret = cModule.ccall(
392 | 'ec_verify_schnorr',
393 | 'number',
394 | ['number', 'number', 'number', 'number'],
395 | [
396 | memory.charStar(publicKey),
397 | memory.charStar(message),
398 | message.length,
399 | memory.charStar(signature),
400 | ]
401 | );
402 | memory.free();
403 | return ret === 1;
404 | };
405 | }
406 |
407 | function pointAddScalar(
408 | cModule: CModule
409 | ): Secp256k1ZKP['ecc']['pointAddScalar'] {
410 | return function (point: Uint8Array, tweak: Uint8Array, compressed = true) {
411 | if (!point || !(point instanceof Uint8Array) || point.length !== 33) {
412 | throw new TypeError('point must be a Uint8Array of length 33');
413 | }
414 | if (!tweak || !(tweak instanceof Uint8Array) || tweak.length !== 32) {
415 | throw new TypeError('tweak must be a Uint8Array of length 32');
416 | }
417 | const memory = new Memory(cModule);
418 |
419 | const lenghtPtr = memory.malloc(8);
420 | const outputLen = compressed ? 33 : 65;
421 | cModule.setValue(lenghtPtr, outputLen, 'i64');
422 | const output = memory.malloc(outputLen);
423 |
424 | const ret = cModule.ccall(
425 | 'ec_point_add_scalar',
426 | 'number',
427 | ['number', 'number', 'number', 'number', 'number'],
428 | [
429 | output,
430 | lenghtPtr,
431 | memory.charStar(point),
432 | memory.charStar(tweak),
433 | compressed ? 1 : 0,
434 | ]
435 | );
436 | if (ret === 1) {
437 | const res = new Uint8Array(
438 | cModule.HEAPU8.subarray(output, output + outputLen)
439 | );
440 | memory.free();
441 | return res;
442 | }
443 | memory.free();
444 | return null;
445 | };
446 | }
447 |
448 | export function ecc(cModule: CModule): Secp256k1ZKP['ecc'] {
449 | return {
450 | isPoint: isPoint(cModule),
451 | pointAddScalar: pointAddScalar(cModule),
452 | isPrivate: isPrivate(cModule),
453 | pointCompress: pointCompress(cModule),
454 | pointFromScalar: pointFromScalar(cModule),
455 | privateAdd: privateAdd(cModule),
456 | privateMul: privateMul(cModule),
457 | privateSub: privateSub(cModule),
458 | privateNegate: privateNegate(cModule),
459 | sign: signECDSA(cModule),
460 | verify: verifyECDSA(cModule),
461 | signSchnorr: signSchnorr(cModule),
462 | verifySchnorr: verifySchnorr(cModule),
463 | xOnlyPointAddTweak: xOnlyPointAddTweak(cModule),
464 | };
465 | }
466 |
--------------------------------------------------------------------------------
/src/main.c:
--------------------------------------------------------------------------------
1 | #include "stdio.h"
2 | #include "stdlib.h"
3 | #include "string.h"
4 | #include "secp256k1.h"
5 | #include "secp256k1_ecdh.h"
6 | #include "secp256k1_musig.h"
7 | #include "secp256k1_generator.h"
8 | #include "secp256k1_rangeproof.h"
9 | #include "secp256k1_preallocated.h"
10 | #include "secp256k1_surjectionproof.h"
11 | #include "secp256k1_extrakeys.h"
12 | #include "secp256k1_schnorrsig.h"
13 |
14 | #ifndef SECP256K1_CONTEXT_ALL
15 | #define SECP256K1_CONTEXT_ALL SECP256K1_CONTEXT_NONE | SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY
16 | #endif
17 |
18 | int ecdh(unsigned char *output, const unsigned char *pubkey, const unsigned char *scalar)
19 | {
20 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
21 | secp256k1_pubkey point;
22 | if (!secp256k1_ec_pubkey_parse(ctx, &point, pubkey, 33))
23 | return 0;
24 | int ret = secp256k1_ecdh(ctx, output, &point, scalar, NULL, NULL);
25 | secp256k1_context_destroy(ctx);
26 | return ret;
27 | }
28 |
29 | int generator_generate(unsigned char *output, const unsigned char *random_seed32)
30 | {
31 | secp256k1_generator gen;
32 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
33 | int ret = secp256k1_generator_generate(ctx, &gen, random_seed32);
34 | if (!ret)
35 | {
36 | secp256k1_context_destroy(ctx);
37 | return ret;
38 | }
39 |
40 | ret = secp256k1_generator_serialize(ctx, output, &gen);
41 | secp256k1_context_destroy(ctx);
42 | return ret;
43 | }
44 |
45 | int generator_generate_blinded(unsigned char *output, const unsigned char *key, const unsigned char *blinder)
46 | {
47 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
48 | secp256k1_generator gen;
49 | int ret = secp256k1_generator_generate_blinded(ctx, &gen, key, blinder);
50 | if (!ret)
51 | {
52 | secp256k1_context_destroy(ctx);
53 | return ret;
54 | }
55 |
56 | ret = secp256k1_generator_serialize(ctx, output, &gen);
57 | secp256k1_context_destroy(ctx);
58 | return ret;
59 | }
60 |
61 | int pedersen_blind_generator_blind_sum(const uint64_t *values, const unsigned char *const *generator_blinds, unsigned char **blind_factors, size_t n_total, size_t n_inputs, unsigned char *bytes_out)
62 | {
63 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
64 | blind_factors[n_total - 1] = bytes_out;
65 | int ret = secp256k1_pedersen_blind_generator_blind_sum(ctx, values, generator_blinds, (unsigned char *const *)blind_factors, n_total, n_inputs);
66 | secp256k1_context_destroy(ctx);
67 | return ret;
68 | }
69 |
70 | int pedersen_commitment(unsigned char *output, uint64_t *value, const unsigned char *generator, const unsigned char *blinder)
71 | {
72 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
73 | secp256k1_generator gen;
74 |
75 | int ret = secp256k1_generator_parse(ctx, &gen, generator);
76 | if (!ret)
77 | {
78 | secp256k1_context_destroy(ctx);
79 | return ret;
80 | }
81 |
82 | secp256k1_pedersen_commitment commit;
83 | ret = secp256k1_pedersen_commit(ctx, &commit, blinder, *value, &gen);
84 | if (!ret)
85 | {
86 | secp256k1_context_destroy(ctx);
87 | return ret;
88 | }
89 |
90 | ret = secp256k1_pedersen_commitment_serialize(ctx, output, &commit);
91 | secp256k1_context_destroy(ctx);
92 | return ret;
93 | }
94 |
95 | int rangeproof_sign(
96 | unsigned char *proof,
97 | size_t *plen,
98 | uint64_t *value,
99 | const unsigned char *commit_data,
100 | const unsigned char *generator_data,
101 | const unsigned char *blind,
102 | const unsigned char *nonce,
103 | int exp,
104 | int min_bits,
105 | uint64_t *min_value,
106 | const unsigned char *message,
107 | size_t msg_len,
108 | const unsigned char *extra_commit,
109 | size_t extra_commit_len)
110 | {
111 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
112 | secp256k1_pedersen_commitment commit;
113 | int ret = secp256k1_pedersen_commitment_parse(ctx, &commit, commit_data);
114 | if (!ret)
115 | {
116 | secp256k1_context_destroy(ctx);
117 | return ret;
118 | }
119 |
120 | secp256k1_generator gen;
121 | ret = secp256k1_generator_parse(ctx, &gen, generator_data);
122 | if (!ret)
123 | {
124 | secp256k1_context_destroy(ctx);
125 | return ret;
126 | }
127 |
128 | ret = secp256k1_rangeproof_sign(ctx, proof, plen, *min_value, &commit, blind, nonce, exp, min_bits, *value, msg_len > 0 ? message : NULL, msg_len, extra_commit_len > 0 ? extra_commit : NULL, extra_commit_len, &gen);
129 | secp256k1_context_destroy(ctx);
130 | return ret;
131 | }
132 |
133 | int rangeproof_info(int *exp, int *mantissa, uint64_t *min_value, uint64_t *max_value, const unsigned char *proof, size_t plen)
134 | {
135 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
136 | int ret = secp256k1_rangeproof_info(ctx, exp, mantissa, min_value, max_value, proof, plen);
137 | secp256k1_context_destroy(ctx);
138 | return ret;
139 | }
140 |
141 | int rangeproof_verify(uint64_t *min_value, uint64_t *max_value, const unsigned char *proof, size_t plen, const unsigned char *commit_data, const unsigned char *generator_data, const unsigned char *extra_commit, size_t extra_commit_len)
142 | {
143 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
144 | secp256k1_pedersen_commitment commit;
145 | int ret = secp256k1_pedersen_commitment_parse(ctx, &commit, commit_data);
146 | if (!ret)
147 | {
148 | secp256k1_context_destroy(ctx);
149 | return ret;
150 | }
151 |
152 | secp256k1_generator gen;
153 | ret = secp256k1_generator_parse(ctx, &gen, generator_data);
154 | if (!ret)
155 | {
156 | secp256k1_context_destroy(ctx);
157 | return ret;
158 | }
159 |
160 | ret = secp256k1_rangeproof_verify(ctx, min_value, max_value, &commit, proof, plen, extra_commit, extra_commit_len, &gen);
161 | secp256k1_context_destroy(ctx);
162 | return ret;
163 | }
164 |
165 | int rangeproof_rewind(unsigned char *blind_out, uint64_t *value_out, uint64_t *min_value, uint64_t *max_value, unsigned char *message_out, size_t *outlen, const unsigned char *proof, size_t plen, const unsigned char *commit_data, const unsigned char *generator_data, const unsigned char *nonce, const unsigned char *extra_commit, size_t extra_commit_len)
166 | {
167 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
168 | secp256k1_pedersen_commitment commit;
169 | int ret = secp256k1_pedersen_commitment_parse(ctx, &commit, commit_data);
170 | if (!ret)
171 | {
172 | secp256k1_context_destroy(ctx);
173 | return ret;
174 | }
175 |
176 | secp256k1_generator gen;
177 | ret = secp256k1_generator_parse(ctx, &gen, generator_data);
178 | if (!ret)
179 | {
180 | secp256k1_context_destroy(ctx);
181 | return ret;
182 | }
183 |
184 | ret = secp256k1_rangeproof_rewind(ctx, blind_out, value_out, message_out, outlen, nonce, min_value, max_value, &commit, proof, plen, extra_commit, extra_commit_len, &gen);
185 | secp256k1_context_destroy(ctx);
186 | return ret;
187 | }
188 |
189 | int surjectionproof_initialize(unsigned char *output, size_t *outputlen, size_t *input_index, const unsigned char *const *input_tags_data, const size_t n_input_tags, const size_t n_input_tags_to_use, const unsigned char *output_tag_data, const size_t n_max_iterations, const unsigned char *random_seed32)
190 | {
191 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
192 | secp256k1_fixed_asset_tag input_tags[n_input_tags];
193 | for (int i = 0; i < (int)n_input_tags; ++i)
194 | {
195 | memcpy(&(input_tags[i].data), input_tags_data[i], 32);
196 | }
197 |
198 | secp256k1_fixed_asset_tag output_tag;
199 | memcpy(&(output_tag.data), output_tag_data, 32);
200 |
201 | secp256k1_surjectionproof proof;
202 | int ret = secp256k1_surjectionproof_initialize(ctx, &proof, input_index, input_tags, n_input_tags, n_input_tags_to_use, &output_tag, n_max_iterations, random_seed32);
203 | if (!ret)
204 | {
205 | secp256k1_context_destroy(ctx);
206 | return ret;
207 | }
208 |
209 | ret = secp256k1_surjectionproof_serialize(ctx, output, outputlen, &proof);
210 | secp256k1_context_destroy(ctx);
211 | return ret;
212 | }
213 |
214 | int surjectionproof_generate(unsigned char *output, size_t *outputlen, const unsigned char *proof_data, const size_t proof_len, const unsigned char *const *ephemeral_input_tags_data, const size_t n_ephemeral_input_tags, const unsigned char *ephemeral_output_tag_data, size_t input_index, const unsigned char *input_blinding_key, const unsigned char *output_blinding_key)
215 | {
216 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
217 | secp256k1_surjectionproof proof;
218 | int ret = secp256k1_surjectionproof_parse(ctx, &proof, proof_data, proof_len);
219 | if (!ret)
220 | {
221 | secp256k1_context_destroy(ctx);
222 | return ret;
223 | }
224 |
225 | secp256k1_generator ephemeral_input_tags[n_ephemeral_input_tags];
226 | for (int i = 0; i < (int)n_ephemeral_input_tags; ++i)
227 | {
228 | int ret = secp256k1_generator_parse(ctx, &ephemeral_input_tags[i], ephemeral_input_tags_data[i]);
229 | if (!ret)
230 | {
231 | secp256k1_context_destroy(ctx);
232 | return ret;
233 | }
234 | }
235 | secp256k1_generator ephemeral_output_tag;
236 | ret = secp256k1_generator_parse(ctx, &ephemeral_output_tag, ephemeral_output_tag_data);
237 | if (!ret)
238 | {
239 | secp256k1_context_destroy(ctx);
240 | return ret;
241 | }
242 |
243 | ret = secp256k1_surjectionproof_generate(ctx, &proof, ephemeral_input_tags, n_ephemeral_input_tags, &ephemeral_output_tag, input_index, input_blinding_key, output_blinding_key);
244 | if (!ret)
245 | {
246 | secp256k1_context_destroy(ctx);
247 | return ret;
248 | }
249 |
250 | ret = secp256k1_surjectionproof_serialize(ctx, output, outputlen, &proof);
251 | secp256k1_context_destroy(ctx);
252 | return ret;
253 | }
254 |
255 | int surjectionproof_verify(const unsigned char *proof_data, const size_t proof_len, const unsigned char *const *ephemeral_input_tags_data, const size_t n_ephemeral_input_tags, const unsigned char *ephemeral_output_tag_data)
256 | {
257 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
258 | secp256k1_surjectionproof proof;
259 | int ret = secp256k1_surjectionproof_parse(ctx, &proof, proof_data, proof_len);
260 | if (!ret)
261 | {
262 | secp256k1_context_destroy(ctx);
263 | return ret;
264 | }
265 |
266 | secp256k1_generator ephemeral_input_tags[n_ephemeral_input_tags];
267 | for (int i = 0; i < (int)n_ephemeral_input_tags; ++i)
268 | {
269 | int ret = secp256k1_generator_parse(ctx, &ephemeral_input_tags[i], ephemeral_input_tags_data[i]);
270 | if (!ret)
271 | {
272 | secp256k1_context_destroy(ctx);
273 | return ret;
274 | }
275 | }
276 | secp256k1_generator ephemeral_output_tag;
277 | ret = secp256k1_generator_parse(ctx, &ephemeral_output_tag, ephemeral_output_tag_data);
278 | if (!ret)
279 | {
280 | secp256k1_context_destroy(ctx);
281 | return ret;
282 | }
283 |
284 | ret = secp256k1_surjectionproof_verify(ctx, &proof, ephemeral_input_tags, n_ephemeral_input_tags, &ephemeral_output_tag);
285 | secp256k1_context_destroy(ctx);
286 | return ret;
287 | }
288 |
289 | int ec_seckey_negate(unsigned char *key)
290 | {
291 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
292 | int ret = secp256k1_ec_seckey_negate(ctx, key);
293 | secp256k1_context_destroy(ctx);
294 | return ret;
295 | }
296 |
297 | int ec_seckey_tweak_add(unsigned char *key, const unsigned char *tweak)
298 | {
299 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
300 | int ret = secp256k1_ec_seckey_tweak_add(ctx, key, tweak);
301 | secp256k1_context_destroy(ctx);
302 | return ret;
303 | }
304 |
305 | int ec_seckey_tweak_mul(unsigned char *key, const unsigned char *tweak)
306 | {
307 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
308 | int ret = secp256k1_ec_seckey_tweak_mul(ctx, key, tweak);
309 | secp256k1_context_destroy(ctx);
310 | return ret;
311 | }
312 |
313 | int ec_seckey_tweak_sub(unsigned char *key, const unsigned char *tweak)
314 | {
315 | unsigned char *t = malloc(32);
316 | memcpy(t, tweak, 32);
317 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
318 | int ret = secp256k1_ec_seckey_negate(ctx, t);
319 | if (ret == 1)
320 | {
321 | ret = secp256k1_ec_seckey_tweak_add(ctx, key, (const unsigned char *)t);
322 | }
323 | secp256k1_context_destroy(ctx);
324 | return ret;
325 | }
326 |
327 | int ec_is_valid_xonly_pubkey(const unsigned char *key)
328 | {
329 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
330 | secp256k1_xonly_pubkey pubkey;
331 | int ret = secp256k1_xonly_pubkey_parse(ctx, &pubkey, key);
332 | secp256k1_context_destroy(ctx);
333 | return ret;
334 | }
335 |
336 | int ec_is_valid_pubkey(const unsigned char *key, size_t key_len)
337 | {
338 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
339 | secp256k1_pubkey pubkey;
340 | int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, key, key_len);
341 | secp256k1_context_destroy(ctx);
342 | return ret;
343 | }
344 |
345 | int ec_is_point(const unsigned char *key, size_t key_len)
346 | {
347 | if (key_len == 32)
348 | {
349 | return ec_is_valid_xonly_pubkey(key);
350 | }
351 |
352 | return ec_is_valid_pubkey(key, key_len);
353 | }
354 |
355 | int ec_point_compress(unsigned char *output, size_t *output_len, const unsigned char *point, size_t point_len, int compress)
356 | {
357 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
358 | secp256k1_pubkey pubkey;
359 | int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, point, point_len);
360 | if (ret == 1)
361 | {
362 | ret = secp256k1_ec_pubkey_serialize(ctx, output, output_len, &pubkey, compress ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);
363 | }
364 | secp256k1_context_destroy(ctx);
365 | return ret;
366 | }
367 |
368 | int ec_point_from_scalar(unsigned char *output, size_t *output_len, const unsigned char *scalar, int compress)
369 | {
370 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
371 | secp256k1_pubkey pubkey;
372 | int ret = secp256k1_ec_pubkey_create(ctx, &pubkey, scalar);
373 | if (ret == 1)
374 | {
375 | ret = secp256k1_ec_pubkey_serialize(ctx, output, output_len, &pubkey, compress ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);
376 | }
377 | secp256k1_context_destroy(ctx);
378 | return ret;
379 | }
380 |
381 | int ec_x_only_point_tweak_add(unsigned char *output, int *parity, const unsigned char *point, const unsigned char *tweak)
382 | {
383 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
384 | secp256k1_xonly_pubkey pubkey;
385 | secp256k1_pubkey pubkey_result;
386 | int ret = secp256k1_xonly_pubkey_parse(ctx, &pubkey, point);
387 | if (ret == 1)
388 | {
389 | ret = secp256k1_xonly_pubkey_tweak_add(ctx, &pubkey_result, &pubkey, tweak);
390 | if (ret == 1)
391 | {
392 | ret = secp256k1_xonly_pubkey_from_pubkey(ctx, &pubkey, parity, &pubkey_result);
393 | if (ret == 1)
394 | {
395 | ret = secp256k1_xonly_pubkey_serialize(ctx, output, &pubkey);
396 | }
397 | }
398 | }
399 | secp256k1_context_destroy(ctx);
400 | return ret;
401 | }
402 |
403 | int ec_sign_ecdsa(unsigned char *output, const unsigned char *d, const unsigned char *h, int withextradata, const unsigned char *e)
404 | {
405 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
406 | secp256k1_ecdsa_signature sig;
407 | int ret = secp256k1_ecdsa_sign(ctx, &sig, h, d, secp256k1_nonce_function_rfc6979, withextradata ? e : NULL);
408 | if (ret == 1)
409 | {
410 | ret = secp256k1_ecdsa_signature_serialize_compact(ctx, output, &sig);
411 | }
412 | secp256k1_context_destroy(ctx);
413 | return ret;
414 | }
415 |
416 | int ec_verify_ecdsa(const unsigned char *q, size_t q_len, const unsigned char *h, const unsigned char *sig, const int strict)
417 | {
418 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
419 | secp256k1_ecdsa_signature sig_parsed;
420 | secp256k1_pubkey pubkey;
421 | int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, q, q_len);
422 | if (ret == 1)
423 | {
424 | ret = secp256k1_ecdsa_signature_parse_compact(ctx, &sig_parsed, sig);
425 | if (ret == 1)
426 | {
427 | if (strict == 0)
428 | {
429 | ret = secp256k1_ecdsa_signature_normalize(ctx, &sig_parsed, &sig_parsed);
430 | }
431 | ret = secp256k1_ecdsa_verify(ctx, &sig_parsed, h, &pubkey);
432 | }
433 | }
434 | secp256k1_context_destroy(ctx);
435 | return ret;
436 | }
437 |
438 | int ec_sign_schnorr(unsigned char *output, const unsigned char *d, const unsigned char *h, const int withextradata, const unsigned char *e)
439 | {
440 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
441 | secp256k1_keypair key;
442 | int ret = secp256k1_keypair_create(ctx, &key, d);
443 | if (ret == 1)
444 | {
445 | ret = secp256k1_schnorrsig_sign32(ctx, output, h, &key, withextradata ? e : NULL);
446 | }
447 | secp256k1_context_destroy(ctx);
448 | return ret;
449 | }
450 |
451 | int ec_verify_schnorr(const unsigned char *q, const unsigned char *h, size_t h_len, const unsigned char *sig)
452 | {
453 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
454 | secp256k1_xonly_pubkey pubkey;
455 | int ret = secp256k1_xonly_pubkey_parse(ctx, &pubkey, q);
456 | if (ret == 1)
457 | {
458 | ret = secp256k1_schnorrsig_verify(ctx, sig, h, h_len, &pubkey);
459 | }
460 | secp256k1_context_destroy(ctx);
461 | return ret;
462 | }
463 |
464 | int ec_seckey_verify(const unsigned char *seckey)
465 | {
466 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
467 | int ret = secp256k1_ec_seckey_verify(ctx, seckey);
468 | secp256k1_context_destroy(ctx);
469 | return ret;
470 | }
471 |
472 | int isZero(const unsigned char *array, size_t size)
473 | {
474 | for (size_t i = 0; i < size; i++)
475 | {
476 | if (array[i] != 0)
477 | {
478 | return 0;
479 | }
480 | }
481 | return 1;
482 | }
483 |
484 | int ec_point_add_scalar(unsigned char *output, size_t *output_len, const unsigned char *point, const unsigned char *tweak, int compress)
485 | {
486 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL);
487 | secp256k1_pubkey pubkey;
488 | int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, point, 33);
489 | if (ret == 1)
490 | {
491 | // check if the tweak is zero
492 | if (isZero(tweak, 32) == 1)
493 | {
494 | // if so, just serialize the pubkey
495 | ret = secp256k1_ec_pubkey_serialize(ctx, output, output_len, &pubkey, compress ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);
496 | }
497 | else
498 | {
499 | // otherwise, add the tweak to the pubkey
500 | ret = secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, tweak);
501 | if (ret == 1)
502 | {
503 | ret = secp256k1_ec_pubkey_serialize(ctx, output, output_len, &pubkey, compress ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);
504 | }
505 | }
506 | }
507 | secp256k1_context_destroy(ctx);
508 | return ret;
509 | }
510 |
511 | void **alloc_pointer_arr(size_t n, size_t elem_size)
512 | {
513 | void **arr = malloc(sizeof(void *) * n);
514 | for (int i = 0; i < n; i++)
515 | {
516 | arr[i] = malloc(elem_size);
517 | }
518 | return arr;
519 | }
520 |
521 | void free_pointer_arr(void **ptrs, size_t n)
522 | {
523 | for (int i = 0; i < n; i++)
524 | {
525 | free(ptrs[i]);
526 | }
527 | free(ptrs);
528 | }
529 |
530 | #define RETURN_ON_ZERO \
531 | if (ret == 0) \
532 | { \
533 | secp256k1_context_destroy(ctx); \
534 | return ret; \
535 | }
536 |
537 | int musig_pubkey_agg(
538 | unsigned char *agg_pubkey,
539 | secp256k1_musig_keyagg_cache *keyagg_cache,
540 | const unsigned char **pubkeys,
541 | const size_t n_pubkeys,
542 | const size_t pubkey_len)
543 | {
544 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
545 | secp256k1_pubkey **pubkeys_ptr = (secp256k1_pubkey **)alloc_pointer_arr(n_pubkeys, sizeof(secp256k1_pubkey));
546 |
547 | int ret = 1;
548 | for (int i = 0; i < n_pubkeys && ret == 1; i++)
549 | {
550 | ret = secp256k1_ec_pubkey_parse(ctx, pubkeys_ptr[i], pubkeys[i], pubkey_len);
551 | }
552 |
553 | if (ret == 1)
554 | {
555 | secp256k1_xonly_pubkey agg_pubkey_temp;
556 | ret = secp256k1_musig_pubkey_agg(ctx, NULL, &agg_pubkey_temp, keyagg_cache, (const secp256k1_pubkey *const *)pubkeys_ptr, n_pubkeys);
557 |
558 | if (ret == 1)
559 | {
560 | ret = secp256k1_xonly_pubkey_serialize(ctx, agg_pubkey, &agg_pubkey_temp);
561 | }
562 | }
563 |
564 | free_pointer_arr((void **)pubkeys_ptr, n_pubkeys);
565 | secp256k1_context_destroy(ctx);
566 | return ret;
567 | }
568 |
569 | int musig_nonce_gen(
570 | secp256k1_musig_secnonce *secnonce,
571 | unsigned char *pubnonce,
572 | const unsigned char *session_id32,
573 | const unsigned char *pubkey,
574 | const size_t pubkey_len)
575 | {
576 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
577 |
578 | secp256k1_pubkey pubkey_temp;
579 | int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey_temp, pubkey, pubkey_len);
580 | RETURN_ON_ZERO;
581 |
582 | secp256k1_musig_pubnonce pubnonce_temp;
583 | ret = secp256k1_musig_nonce_gen(ctx, secnonce, &pubnonce_temp, session_id32, NULL, &pubkey_temp, NULL, NULL, NULL);
584 | RETURN_ON_ZERO;
585 |
586 | ret = secp256k1_musig_pubnonce_serialize(ctx, pubnonce, &pubnonce_temp);
587 |
588 | secp256k1_context_destroy(ctx);
589 | return ret;
590 | }
591 |
592 | int musig_nonce_agg(unsigned char *aggnonce, const unsigned char *const *pubnonces, size_t n_pubnonces)
593 | {
594 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
595 | secp256k1_musig_pubnonce **pubnonces_ptr = (secp256k1_musig_pubnonce **)alloc_pointer_arr(n_pubnonces, sizeof(secp256k1_musig_pubnonce));
596 |
597 | int ret = 1;
598 | for (int i = 0; i < n_pubnonces && ret == 1; i++)
599 | {
600 | ret = secp256k1_musig_pubnonce_parse(ctx, pubnonces_ptr[i], pubnonces[i]);
601 | }
602 |
603 | if (ret == 1)
604 | {
605 | secp256k1_musig_aggnonce aggnonce_temp;
606 | ret = secp256k1_musig_nonce_agg(ctx, &aggnonce_temp, (const secp256k1_musig_pubnonce *const *)pubnonces_ptr, n_pubnonces);
607 |
608 | if (ret == 1)
609 | {
610 | ret = secp256k1_musig_aggnonce_serialize(ctx, aggnonce, &aggnonce_temp);
611 | }
612 | }
613 |
614 | free_pointer_arr((void **)pubnonces_ptr, n_pubnonces);
615 | secp256k1_context_destroy(ctx);
616 | return ret;
617 | }
618 |
619 | int musig_nonce_process(secp256k1_musig_session *session, const unsigned char *aggnonce_serialized, const unsigned char *msg32, const secp256k1_musig_keyagg_cache *keyagg_cache)
620 | {
621 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
622 |
623 | secp256k1_musig_aggnonce aggnonce;
624 | int ret = secp256k1_musig_aggnonce_parse(ctx, &aggnonce, aggnonce_serialized);
625 | RETURN_ON_ZERO;
626 |
627 | ret = secp256k1_musig_nonce_process(ctx, session, &aggnonce, msg32, keyagg_cache, NULL);
628 |
629 | secp256k1_context_destroy(ctx);
630 | return ret;
631 | }
632 |
633 | int musig_partial_sign(unsigned char *partial_sig, secp256k1_musig_secnonce *secnonce, const unsigned char *seckey, const secp256k1_musig_keyagg_cache *keyagg_cache, const secp256k1_musig_session *session)
634 | {
635 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
636 |
637 | secp256k1_keypair keypair;
638 | int ret = secp256k1_keypair_create(ctx, &keypair, seckey);
639 | RETURN_ON_ZERO;
640 |
641 | secp256k1_musig_partial_sig sig_temp;
642 | ret = secp256k1_musig_partial_sign(ctx, &sig_temp, secnonce, &keypair, keyagg_cache, session);
643 | RETURN_ON_ZERO;
644 |
645 | ret = secp256k1_musig_partial_sig_serialize(ctx, partial_sig, &sig_temp);
646 |
647 | secp256k1_context_destroy(ctx);
648 | return ret;
649 | }
650 |
651 | int musig_partial_sig_verify(
652 | const unsigned char *partial_sig,
653 | const unsigned char *pubnonce,
654 | const unsigned char *pubkey,
655 | const size_t pubkey_len,
656 | const secp256k1_musig_keyagg_cache *keyagg_cache,
657 | const secp256k1_musig_session *session)
658 | {
659 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
660 |
661 | secp256k1_musig_partial_sig sig_temp;
662 | int ret = secp256k1_musig_partial_sig_parse(ctx, &sig_temp, partial_sig);
663 | RETURN_ON_ZERO;
664 |
665 | secp256k1_musig_pubnonce pubnonce_temp;
666 | ret = secp256k1_musig_pubnonce_parse(ctx, &pubnonce_temp, pubnonce);
667 | RETURN_ON_ZERO;
668 |
669 | secp256k1_pubkey pubkey_temp;
670 | ret = secp256k1_ec_pubkey_parse(ctx, &pubkey_temp, pubkey, pubkey_len);
671 | RETURN_ON_ZERO;
672 |
673 | ret = secp256k1_musig_partial_sig_verify(ctx, &sig_temp, &pubnonce_temp, &pubkey_temp, keyagg_cache, session);
674 |
675 | secp256k1_context_destroy(ctx);
676 | return ret;
677 | }
678 |
679 | int musig_partial_sig_agg(
680 | unsigned char *sig,
681 | const secp256k1_musig_session *session,
682 | unsigned char **partial_sigs,
683 | size_t n_sigs)
684 | {
685 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
686 | secp256k1_musig_partial_sig **sigs_ptr = (secp256k1_musig_partial_sig **)alloc_pointer_arr(n_sigs, sizeof(secp256k1_musig_partial_sig));
687 |
688 | int ret = 1;
689 | for (int i = 0; i < n_sigs && ret == 1; i++)
690 | {
691 | ret = secp256k1_musig_partial_sig_parse(ctx, sigs_ptr[i], partial_sigs[i]);
692 | }
693 |
694 | if (ret == 1)
695 | {
696 | ret = secp256k1_musig_partial_sig_agg(ctx, sig, session, (const secp256k1_musig_partial_sig *const *) sigs_ptr, n_sigs);
697 | }
698 |
699 | free_pointer_arr((void **)sigs_ptr, n_sigs);
700 | secp256k1_context_destroy(ctx);
701 | return ret;
702 | }
703 |
704 | int musig_pubkey_xonly_tweak_add(
705 | unsigned char *output,
706 | size_t *output_len,
707 | int compress,
708 | secp256k1_musig_keyagg_cache *keyagg_cache,
709 | const unsigned char *tweak)
710 | {
711 | secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
712 |
713 | secp256k1_pubkey output_temp;
714 | int ret = secp256k1_musig_pubkey_xonly_tweak_add(ctx, &output_temp, keyagg_cache, tweak);
715 | RETURN_ON_ZERO;
716 |
717 | ret = secp256k1_ec_pubkey_serialize(ctx, output, output_len, &output_temp, compress ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);
718 |
719 | secp256k1_context_destroy(ctx);
720 | return ret;
721 | }
722 |
--------------------------------------------------------------------------------
/src/test/fixtures/surjectionproof.json:
--------------------------------------------------------------------------------
1 | {
2 | "initialize": [
3 | {
4 | "inputTags": [
5 | "e1fa72fab1940a0e11ffd98b87e78cb60af172f9abf8cca8366248686f9d7780",
6 | "3439b1e14fdc7f075fe0a827b7d4aa206ac5dd70c7b1bf73a8094cac6dbafcff",
7 | "4ef4fc96a4ec8583fb57d9cbfcce85a4402eaacc618145b71343857f422b4900",
8 | "b4a2d026eef563f2071d1813a0e5425824824e1a8d5553b8a7bae7eec1363e58",
9 | "9270f1624ab68630df1d324b8e4e749e52159b76d382d6eac5fb137e25fcfd86"
10 | ],
11 | "outputTag": "3439b1e14fdc7f075fe0a827b7d4aa206ac5dd70c7b1bf73a8094cac6dbafcff",
12 | "maxIterations": 500,
13 | "seed": "1be5ddc058fdeaade0c46b1579fcb26fcc50222b8bfe90e92ec14d2461cfa9bb",
14 | "expected": {
15 | "inputIndex": 1,
16 | "proof": "0500160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
17 | }
18 | },
19 | {
20 | "inputTags": [
21 | "81dc794bf3a1fd535299558594427477130eb4dc0b50648294cd917325089cb1",
22 | "e93e919b787678fb5ec232273b2f6bd35d54b4991932c729dfbbd3e258d8a4e9",
23 | "d38080b9cda519e52fad7cb1b024c450bbef9c29637aadd828e914dd856cdc27",
24 | "c12d23ea443af3d2acb20512051667e2a959fa09539b8a58fc7e1ada47d6fd04",
25 | "5e66664697d1d71e14a01cb89b48325a9a25a73a912381882990f68592eb2c6b",
26 | "122cbf467ab4a1847d80cd9cebb68d8b2376899666b51ae388be1082bb70d77e",
27 | "c18654416f77db72a906dbd309be451c68f5c1cf9ee3bac1977047f03e878b94",
28 | "4701b4048d7b6806c8a77dd998272e4807baa2265ba2abbda6113d91ef0b519a",
29 | "7ddf90dc4ca1935e47fe166655ca5f6fbe79f7ff29f147d82a638cfd4c261b19",
30 | "2861c9b8287b27451551c87c5f3197ea6d8fe7c0022b71125c9e3828f7b81e20",
31 | "f83310f536d3ac56fec49011506075ece641a59a9919db0365a3e5813d69098c",
32 | "12cbd117060374490dc7779477027840f7683ed00861c951a9de7648d989aa26",
33 | "7fc31997ae3956989af66bf284daf3f5f7f1a4e8c1b3d93e15f3b75ac0247df1",
34 | "ac7ac415761c99e0258ffbdde68ef88dd4e4cbe18f0208196018fca0f783e2b6",
35 | "521fc1c61a2c9c839ebe30318e6d3d05459c732c6bb9aa74e7b384f6f59332a1",
36 | "71a3a7a3135de026e5c7dddc22d57db31d4d00c0662f5ee9bfa7164d8cecf2c8",
37 | "5b344189468e428b2b7284daeb25881e8b64f02189b92eca69ae426846c72cce",
38 | "e09c96c85735e46073f06d52ba74d459fdbe1d20a41f52b94df3a6611beedfe6",
39 | "3634e6835d6e62ae4fab43b05dbd26cb7945ddf9f284e3acc86d2f7f128a368d",
40 | "2ae8bad3b760ecb73e39c01b4444198db503f26870c3481a128e85bb7516144c",
41 | "d2daaa8aa1a1b4e7f12df69227e8eef775c3f81f1a42035a78fce7b8abd5c791",
42 | "3be221f5bc3b7804a46b3c0e2fa5b4f2e8fdb955c9973d2309fc6b0ebf1ee398",
43 | "e9e5a2e5e96a6aa0d59e1e3bbd99eb7ecf09eab5090675a963ab57eeafc2fc98",
44 | "d03e0d5d91b4e2d8e1eb594a8dd0d7b007c68e8be89ee169bd5caa302efc7a64",
45 | "ed93234f1f253dc0fed5ffda08eb015cb00c2bab8651e40b1495122a3043628c",
46 | "e4e69c6fb480f459001279cc4a735572c9f2160ea46ea67fd78d9dc6e8f9f236",
47 | "7706f55436a465c27d61030764f756a4d4bec47b4a86420578b3c0acee6daa2e",
48 | "5cd12f6dad7709f8af9e9dfeb2a3a289d22bb92dc26ab6581c5c5d9cc651af5f",
49 | "f015601bb6e171541f55330ad665f168c4374641177f367d32beaa8c7221f0f1",
50 | "f8252e1ecb37d9d6fcc05fbd7fbe29346095a62ba8b1645d3471e6394333746a",
51 | "09fc44fe529f0793d393d054504ba9533b4d7f49eebf540d67b2f8a542d1151b",
52 | "a44574ceb59de6faa6b97716bbf5d1209fa8a6991160eee8192ee7efdbca0173",
53 | "1c9fc353b80e8e622851b4e76714956a326051891f85544c83a79687ccb82177",
54 | "a092d1e8e4f6f49ac1d080669e953c1adb2dcaa6fcda4a59a2e265823be3ab8a",
55 | "56aab5b595b1cabe2e9230892cb00144f84ab9cb59b4050efba170133936ed2b",
56 | "e374b380c13f2136c3b1907d4e58efbb2f26effdde1a9b004948e122b34ffde3",
57 | "6bc1f349a9278bf6baaa385b1b477a91f9700b37ac8b227bf9f31a4cc948243d",
58 | "bb238e108963dbaabcb21473c6e8f11922b06b836e4870e6166194743cc4e064",
59 | "48ac0a0aa2a42f0cf7f168953e9482b47eb8d13ef0f631a410873c99fa849ed8",
60 | "59dd95306f7a8e076f8e2b1a894584a7b465188519ecfb2b8bb1092e62531dd0",
61 | "038bcd31af4060f759818d833127aad81ee4258a83687198bd4652ba1407b321",
62 | "c35c29ead962be3f3f04e7fc3c866aa9493a0dd2112fbc031ecb1f2204bfc61c",
63 | "78b1fd77e0a96ee6a25814df1d6f34f13cf7aa863a5fca40c62e908cd93877a0",
64 | "76291f2d6d9472b2640a70927899f99b1dbd9f5708c203faa0f3797758eacb10",
65 | "cbc3e2172f76f0f1c1b1b46b70ab9744f542775db86fb6f5634afd1a9831a38e",
66 | "e163058f3ab4959d606fa3415c7b7f0b13e18a54f3211a1c04089b743cbe1c02",
67 | "db291ba41c2925945df2ba74dc47a94eb7e9fa1d6ce75f92d0b0cd544ff6d768",
68 | "fc9335af074357bfe7bb4adcba812ec4ad1b52b4e12455a5277ddc948ebe463b",
69 | "fda2f3823c33427a62cc5e22d3cd3e68d81dfdab8e04da9c5004fc22292ec7bc",
70 | "f28e79f8fb31488abf6e94d05cca3f650052a14c9cb9123f997b9c395763aa1b",
71 | "533a61a150eb03362e89fb2922f3baf09f39ee878d40b4c551a5bedc6433e2b7",
72 | "4f17427387e26df3a46ad7f37b347525bce9ed952448648e3e1638b642532994",
73 | "d02433982cd3c0cd51f718d74c0b3bab70777d6ac4b946f6c5d3f13bd21231bc",
74 | "fc128e7aee822089b645856685c3d296c458ad721863be9f86b000108e83b980",
75 | "835b2b83ed9d26d8d3bfc28f84f82db685f72bbff9ea8c34b08302d26b4d6fb7",
76 | "7839e26380773c971a23115b0a0aed3c3640f18e63b645c2486fd215af814f33",
77 | "86e03da968f2fac32cb17bef99bc8bba34471687d4cec9b514e79015cb2c1d3e",
78 | "ae8d2890f214b855eb1a92f73b4e58c6579efde63e386f0280f439cd7b333b11",
79 | "17ed2b6a6b7863f5b9bce4f4654e4c1bd831eb00857670c0102d157f46403447",
80 | "e0925670f5fb8c8a2184d987ad23d35b3642202f4aee8bcd56cb535395912957",
81 | "130c31b90391ca1bb27c5f29e96d3e22f067cbe063b8ec5e0c8695cd01325944",
82 | "2c7cdcd95690d24448395773e6300edcb672bab59f6c1ceaa6e1762caef6d74e",
83 | "85ca8537b12e463af73b5fef389079553f065ba860acf99ca2bea04752aba9f2",
84 | "2c74a7272e63a7b11449bd76303d8ba349ec7185775b8a3d9e51db082717d7fc",
85 | "51dcdeebf3a85e774ddadc4a2909016116f295bfb6a1d47d054c4cb236b589f1",
86 | "58dee9e6cf91fa2d77dfc613da9c1056c9b774b6e9a0c94fd4767904fec2b6dd",
87 | "3c74de962111d5ea301943bacfa149fb143acfb593b28f5939f6832ce834364f",
88 | "8022fbcc353f8d77013853e529548db423f2b9406a1c82d659968bd547ba6ba8",
89 | "eede9288cfa076ebbad9030a29793efec669f3a05dfd0d9881121cb107b915e2",
90 | "55eebcc70f43b724add2e441f1af2174c9374b2f7055194edbdaf5026234f2c9",
91 | "414735fb6b9f036f563b017282e8ae4e056e419e60266cc6e9613639dca05450",
92 | "3f37147ea4c20423a2efd386f2613d25ccd39ff4974a747b532ef6d622fd41ec",
93 | "d9a1b3b57d361bc95bac0109ec64fbb2beaf5cb876d5fa7d8e5677424425ea32",
94 | "0e0fda0c1d4b3b31e6c27b0a1b95aeff9f2589c2ac168c8899c22d97f8f6a618",
95 | "2a68b67a78a67cf9ff8ff87b7c0ca3f1845ae6c88ff21adbf0a39afbc2f98b15",
96 | "dc3ca97d5a5134b65d2b1aaae7125e4d8d76b6d10a5ed381f08662a8ebe6d917",
97 | "4aab67db7f8884a4d1912db2102b73668edeecce49ae50047e9133fe957a3508",
98 | "2a4fdc3462564587545ae93832d2c3ac217f52ca0fae4cf8defc601d1fc74cb2",
99 | "6258fd0e2dde8cd7980a5dcfe6de4e7354c1f53c374c2f346f0505c1fa3399f3",
100 | "c65850d2f7b3c71749f82f186241f2e7ea3789b40e71f62d6db39cfef1f15370",
101 | "6f06b3d858624b43895afc986ada753b46dcbee4de7fcff4b69fc3ca7a53cc95",
102 | "31561c4ce54adb4d521d44b64360b8abbd64090cd0df978b2f71c0e5d9bf1028",
103 | "601fa84ca437f3da111f9be1cd69300ef44905bd5edf0fe7adb29536f8556638",
104 | "560ebaaee96d545154febc42c8905cbd0a7cd1ec45f7e1c64499b389dba5c733",
105 | "cb5f1228c61124970180259b902487660096a66a9aa9504531594902847ac219",
106 | "5be664c75fc7d0359e1661fb9ec7f6422748967d6cb8ac30cc1a8b347e59f92c",
107 | "37fd596f261a287f014b1bf2a5fb17fb4da207c5b030177aa393e5737145b7d1",
108 | "c511e24a71af96a58cf58d8e5a3f2d891c1970cdfc1c58248455ec8a0a9e257f",
109 | "bffff0435e6f7ce143ad474c42f9d919c2639f231094c4935826b32d54280a38",
110 | "b3c290cdd9241b7b9f8cdf74e135e658e51fb97dd8fdeb03992d2e5aa72f1a48",
111 | "68ae09d34a74f1657bac441a92a5adc03a6736e2d4b3ece8c26cf55ebcd21cc8",
112 | "0aa4ec59b8d8ca365f30522a8c25d49b2012858793e8ec148e83a7b0711751a0",
113 | "cc7795e609d8a43acf8f181c01088a2a30ffdb43b0a349a232d7bfb2d40f396b",
114 | "8b0b9be923c585db1eb22f3e6b963ca3f831d6d6f8d1aca8026508f37718d622",
115 | "db68c7785f7946e5297f9765db7bc8faa612ac80f47d5e42eeb43f65ef78c74d",
116 | "0f552b36a4630eb6776eb90eb61397c632d24bd112bc69ea79179f4421fa1a87",
117 | "cf0d2bd460f7f92727d6cb7d90ed1418aa23a9f37c7fce75a32eed1228aaf78b",
118 | "730d27d88c0e5964913d1eedfb9d3ba9d2b690613acf47a472185215c03d449f",
119 | "f71c80a3f9ec80661f1c36095ced501748a861ab92994980229df4022c5acd84",
120 | "42c48a75fe8d881c3d34ba2ef2bfe6be80f66fed82765bcda73d24d8ffc22e57",
121 | "745247408b39685b3d670314627ea6bae6a7ccfa41bb560ccd91f817d90277a6",
122 | "be831b4b16070c65de8e3230d742fd193193e8f1532d57dacc5b8562ecacc7e4",
123 | "6a79a6f6c31248692efe7c203301bad5a17e4c423779caa3c13ba16442d0812e",
124 | "9ae4a64db80f7cc1925138e833536d3bd69fb64743fd8fb6fea523f71df71071",
125 | "df5159ffb0a4e51c8aac70a3aebc41f8e19f54fca084dcc0cee968c933d24882",
126 | "a8742fba59e464db1f48bfa781566ae48b94aaf1a85e6b860549e9a246a0c491",
127 | "d9a6fb14884ddbd09fc82cb45f3706d6e89f4aa35b6670bf09300a1a39c6eb4e",
128 | "54c02cd09a5bd1fc9a9995e5e3a51f6e1834c08c9708c2cdbab154a8d8ba4668",
129 | "71ba6c9e5478ddc082265f38440c6f9783c8d3178f5cec0edb21c68cc58542f6",
130 | "391dc2277149721a2e1b56b1414f5699ede430230083f13c2b4b35d47b9259a9",
131 | "1ade4adc52519ead0180f4fd49bd3e74b1760f63b69997f3465b747161db85f3",
132 | "78647ab1c7529f46f7e109fc9396e383e42e7e5acc4396397135e23dce33c844",
133 | "a7d9444edd8d1e1d4775f54904b157331079e3c7b0d13371442c2e1392bf916d",
134 | "31a0422ea648c9ef8c194337b7c7ed187e2cff441aaf375d56c6dfcb05bc571c",
135 | "5e24db2c5d61b0bc386eac7eaf9ba71ba100964d15834c6671ca5601a1ef45dd",
136 | "5fb4aee1d9a9f5c52c1cafcf1e61989a415d0f7b3ead6d830145fbe11cd7608e",
137 | "6dbcb7f9534c985cbfffc5f770800319e5f84430dabae1bef18d31a32a7f2bce",
138 | "85d5e0915f7388ef24858da42055b1186e1d9aac1b2206adb5e2753528b95ba9",
139 | "50c19a691e0c0064468b5e69283ffcf67cb1bfc715ec927208d69010b351649e",
140 | "4b5c040246837924241d3dee2985c99677d4c9270ec27e57746e251dd80a79c2",
141 | "06c91bfbb94c96ecaaa4c1bbba4b410d8a9a5d057f5c50a89d942cac6384e308",
142 | "a20e850bafb702b56a122fe7dc3f5fce903b16424dd692c43d7b03eba18ffa5d",
143 | "864165ec2d0966ffa938be34f7d098b9290325948f764052906030f67e135249",
144 | "c9dd36ed8293b151a4dcfb6802a6e5a6f370b4b46b7e23872dfd6b1f9e1ed6c9",
145 | "e6195061d4b1a50dad16c16b80cee123815aa1e76ddd2b9e85cca81aa8e4123a",
146 | "96a447a674373068ed467441b24f99782fd28c41ca1accc1651aaf5bd9e3b66a",
147 | "4d473a2b4c806a3d163c031b1a5f51e9a6cfdda6f77152e08a86662502d7941f",
148 | "8eaa8f6dfbe1b0ff3c51d46fdc446b79127fbbdb9c99481621c636f48c86b34e",
149 | "6fd847f76192f0757efd9c0727f8d50a5e87e42be2e91af83694c15faad39c3c",
150 | "554a9068be7381d1aca06df7dfa21dd238c0bfd9a9c014817c49f3b61013ead0",
151 | "0555e46d3f3fd8358e1eb3146f97ff78ba449b8f18126443311e571736e257c4",
152 | "e6b1fd007abe189dbeb37733a0cb09956bb4aa12b1392e8890a5a491a3edb2d3",
153 | "46075846646eef65981f28aa20cdee2835721e2906baa04b4f5e1cd9dbb728b7",
154 | "deb736ee03fd42aec72168cb9ee68af06889402b3822c8ef1ecef3885d67b431",
155 | "ebc96c93d8e84a5dc8349e74e0a3506177dc92e538ecd73ea8073563179683ae",
156 | "d4dc72ec93fb4c8809030d68f57987cdaecbb86414223484e208091faa7d047a",
157 | "85c8351feb9b42d87a7f57df914a4a270a54c7642ffc31f33e231b2e5772c13d",
158 | "0fea3575ab5857af3151e3b1d4dc62a8e474867e7a589ef9410a394311b8575e",
159 | "8e17a63877033395365773717ab37814a260aca1391ea171ee30870ca6939e7d",
160 | "cf5347454403bc1e675b9e886479cf4589caca8681ef4362f17fb3001b51bfe2",
161 | "afaa97ae26661d88388e9be93ea1f257df46f6a64eaedcd7ebcb453a65b31668",
162 | "d8646c421070399af2f749d267ada53aa457958cec56611bd46e06b4b78d00fd",
163 | "de242b293f96045dd56836cb4d98af68284625ebcada80af020fd6fd9c9b0344",
164 | "301840a2f487f2461816fb76895c00c99d087cf08268aaced054ce2af81e821a",
165 | "18fca21b0a3cba295b5ca4691c6873296a6aae2109e72f6a16be7a0aa30eba34",
166 | "1b3d0046d621990f108c43f0ea1911fc98e8471eba8b270a8f729bf20a058d8a",
167 | "2ea85fe5467249f9b427d89fc6c22884625249e9f0c926a3018cdfa6df54716a",
168 | "e59746f38e45a1cb8e8799cee4b8cfa9ef63ca3b046424e51611f02a11d0777f",
169 | "e5ab0488cf1047f014b206a335308241e3b06a563db7e79457b15c0e957fb9a9",
170 | "92493a864c9218cf175085f00e760745ac07132cd582a53728df4502e2184005",
171 | "59f95009f4d26f872418e39bafd964f165071e2f68215f4d71ac0d44c6280739",
172 | "e7299062c97149ad8a1a285c2d327a3be8362b38768685f9aec94a54380bd499",
173 | "ea70f2034a863dc5740aa2bfaa71b564550091fd34d86d473b03211a27b3949a",
174 | "bf262d6eced1323db2a1e57275776b2cdf71e1bb8e89b23fa653d33e7d76e866",
175 | "6565f0ad4180880cb881c684e45d19a2498d4b2e29df29db4c3ad96aec125899",
176 | "8e93274227dcb516b652f18eac490539a26c18ffd6f90e52105e43a0e3712a6f",
177 | "f298f16d9626b500a7fa84426e7341fa1b63b20c3feb78fe7c0acfdb2d259137",
178 | "1b80ced6b148020e2dc91064229a48ab12c309d435861dfcfa8745fc1dbe50bd",
179 | "64a108a34a39a006ea4fa5077200368d067001e7c3bb57b2e71e71d6f9c3a80c",
180 | "de198018813fcffb301ffffadf738e47c1d980b0fae1eabb68488eb95430058c",
181 | "2fa2198f0292c9c4472279c11cf0d6099a04a2d27e628f9949125d70de60a7a3",
182 | "6bacf3c023b351efa38cd7a7965a6c053fdf5705ac6a82dcf3514eb69d96ea7f",
183 | "648103d19a73171a8edb5191325e55b1eb0062b3439ad37f13ff56820c0a6bc8",
184 | "a436735881719e9c57819a78ab5e42adcc58c9de5e47f045f35f3eeb59567b67",
185 | "0222e286465292c206fd46f23bf621d0ca6121a27c4180760c6729bf64d31daf",
186 | "4b6c209a0b0a388d5042843ae92a7b077329ab3b5862294e93a347fc6edf1b7e",
187 | "306c9a89fc6d93f21dc758197f8100619b9929749cda95449ad39c937a6e8414",
188 | "12fd03b26952d6e615fb65936da3d8b9a5f31f115984de400afc58fd60948cf7",
189 | "41d1d0759b7899b74fa1e564457a172b5a0150413de44f43ae12032f05ab72e9",
190 | "939c235d8921b8f1ec3fea4e70e2807e95eab1a5756846115f289bd3abeb3148",
191 | "3b68acd9cd9c218e10c745282751491164c2fd7d91355df5c69bbc4d0b69836c",
192 | "e70a8fdb9eb841389b7942abcab9939ab6718e3b6f45e423378d059a7ad0ca7e",
193 | "b6e9b4ce3c8c850304db2ffdf4f7e52ad286c2464e8a5be10564abbd4e8cd922",
194 | "a5b74c963da0fb5e3fe9bb85c545b8ead009f75b125db03b10c8754eb9fe9b9d",
195 | "bc5d0da3b51db29d82e38bf210f144d89092450b55dc2570e2023ff000451a39",
196 | "93258747567a16fc8d78f85defd4ce6477ebb2111951cb765379a6234647f58b",
197 | "e98db7ef3ec7c97f5e3a213e225118538a842339eeef996b4293348cb7fce6c0",
198 | "18996228cdb8b4acbe859f4126a77c00d7094cceffc09df72bfc9cf93315279b",
199 | "7ad869c325cb1ac692f33c933517710f814c0294f6d7ce10fd9a31ad5c199e78",
200 | "cf15e3ecded99acf133a3cb31269a687ee0329298a96e2cc8ea6a980b3b58a97",
201 | "b4cae02a92fb8aafc46eb5fd04a0719b5031627360b54db2947fbeabe6d307d3",
202 | "ec04dee9015c2ccf435ff38501ab24657cbf85062ed4b567f117af878be076d1",
203 | "47c5c4c670886a154368e36d07d636e6667b8b5ce98ff04755475c7153980a0a",
204 | "3ceff12f90e14046c2dbc6dc2667612a4cb77427e2596d1c99b00eb888d1f362",
205 | "16601ec0c0de307094d0c20bfb1628a9879bb36401c892f6359b308d80abc51d",
206 | "62e11c09cbd3a33321636be5fa90e226797b511ddbc75aa19b68f5ab25cdc857",
207 | "9bab5352cfc76e03ef3c2d0048eb1e09a5962f81e3f8ce952fa1b12ffbaf5753",
208 | "257e3a798d3a0d3aac426aca5df7a7f7b351453d09c3b6ec4056c26a43359389",
209 | "da0cc806f160f743f5e79754aa043a5a757d04bbac6a72e8754fb3cf96c55d26",
210 | "aa0836f1af9261837fdc766f7e0b798f02f57cb5a6163719a736b7aed69bc56c",
211 | "85353891c1a2290d78313789fa3548dd9bd5343e5a7155772779dfdabaeab93c",
212 | "83c531539962ee9aacfd5e4b1be9bb9bfe1e2e8d41adb73d3299aaadc99a118a",
213 | "4308590ba2aeb03d1e4ccdad0e6c941d3aff8db452439482908874471f32e3fc",
214 | "65c7b3fc9265be1c5291ceb17d3934e1022743f0120aa92e0fa7944c87bab195",
215 | "1ebd519dcdaf3099b1872cfb95e8dc2792adcc274b8fa142a1652e61b07cb867",
216 | "32543b1460198541b8a09205bbc15cab04a4a6ff73d60b4cb45695fb359f658c",
217 | "167365126e77413a3aec81f4c4aa3ee6d551be73b4821914b37c0f5830f37057",
218 | "cb4e1dbe52c170906ea4eccaa5b4ac44bf495a211a329784a66fb083bd364ef1",
219 | "99c9a93de2cdc7d38831eaa13412bb67e1f0794c53519a86d1dc1e1cb2899511",
220 | "22a42b0e53c6303a5faa6d70031ae3d0798668f65e473bec07168138823204b2",
221 | "41213309cf3769aa3792ea8708acaf76321f52350c8a126a45ef157e79ae9324",
222 | "bb56309b3ef784817e99f6e66286f77a65c8db70c7d33fb16b27b9cd67bc0ec9",
223 | "c021ef74e70b99e99abcdb5d853f17943908f36412d81ccc7459964dce9973a0",
224 | "aa1b9f8d3c53517947d40e94eac3c07b986abfe80d690f54ef3a09d477b5c942",
225 | "6eeea6ccf1999fc171c921fc3e131dd36f4720e2cf379786fb5f2aab24f076c5",
226 | "c7cbe88b3a4c6e840417ef26f5b9f8c946bbf040c9bae36155a1662601390b72",
227 | "ad2d2dcf75c996a1e2475dcfa905b0468bd2fc01f1703b6b89f8fdef38347f8a",
228 | "a0fa8787ab92108e9c3a4d4666409ef448a4327e9f869926a74e0c8f4d0d2c69",
229 | "3fd7d7897af9bfcc2a3357f196c859df2d561797cd49e7f5f185621fc2206f91",
230 | "169250e9a192229c99d4289f9a4c2e584489edb35468645444dc68eabcf79be0",
231 | "b8c9aef5c9b836cae874ee221bc581dfff09c24943d0ffaecce096095c0f6a7d",
232 | "d44c132b4391737fd83dd107546dca94004126f4085aafe59b8365b987e5e0b0",
233 | "6643b84276ff97563e524fa5925e5f5f5afbe4b8014e62b658b8fa6ddce64c29",
234 | "65d3afc7518704cd850fd80c709154d90282798760d54d7e0f6d5d9f7418c2ee",
235 | "bbd0b0d48d237123e56a85c880c5d65a02faca18143d21067fd3debdf746e858",
236 | "e3afc448c9e6a440b6be1552e70e8194ddeab859491e7f4ff571e5b03e0796d5",
237 | "0b58a5b440d9d45252f2cc4ccf0e7e98b4d6ed9d2c212ca307e1d7c713a874c6",
238 | "0d95d7cb8d3f3359f8e6517aa0027a9c2fe3c2c96705c384e5ce028bff12a56b",
239 | "2c362acd2949b9d71a262367e8ae7c2d49710c7008ea22f464ced6500b1e5916",
240 | "7645d80b152b0d5fbfd5085962f6a2b50012a6663719f4bd6176e18da6c7fb17",
241 | "ae51154683b00db4b5a298c7c43c516c81e2c9eecdb7748b6054aae5d9e9310b",
242 | "f1641831113901b8788cbf151fc08dd67e2312c89ac83521b09c0f631c3c788d",
243 | "00762fb7a8c75e5fb97e2987a10c6f59739c6467b9a0120421bdcf37f78aebbb",
244 | "91467667247baa23cf942691d7d9ee6155d3c216b072d8a4ad7dc0a646e7ae5a",
245 | "554aedc141af2f34b9d40d7dc6a6a8d73b83d1cc995e834c1b0ef63a08467fc2",
246 | "e8720dd933613ebafa1daa70f1c1e71f6a878f929a798201672fc7a28278dc8f",
247 | "7830fc3fa95faba19c0444d7d758e8e560cade3f91f32ac73ec500e351fe8ea5",
248 | "4237d2e0ab86cf0ee49c9c106e933c86a076dae86ef856d29775bd5a38b51dd1",
249 | "a591df91db1661d28ea4ed258bab6a3dbf93a3036bf78a03c867e735b840416d",
250 | "cbd49a7597fa8bb948319f9f55213df05cea676dfd90b0488bf0f41654965111",
251 | "6aad8b1152a7b13179245226115f5776e94fbea471324e5979a652be6314decd",
252 | "4e97e4787ecbcd4e6369d1e56820af6b5afbcbbb234fa8de805732ef68715a3f",
253 | "74d31decb5a79280e5cf8210b144d47732f3714022625686672acccd38e51a42",
254 | "aacb0fab1b4c7f1ff4dcfd7b6ca3cd6dd93a628670184118cdaa534074a40fc1",
255 | "8f0417f2dd1191bc6861f5852c5fca01af2bc0871bf0438a4b50900a8ff760c3",
256 | "497c2b179a170b373a61c5eddd997deda094c695d127d7f92710e4fda3d555ad",
257 | "7c45c9bbe0c3c6e86eaa005041ebfa139c9b1e60db0d9a902e9d3ab1311a0e42",
258 | "d0ef01ee2a3a212a12e8426413a7f85fb68c274b77308cc2612aa04406a82a91",
259 | "57e7749e8cc2b43af9dfbd76543f458e9f92cd35a1245324916924c58a2699fc",
260 | "10612467084f9166d5a251610f7b638df8422c7e3d470cea65192363376dad11",
261 | "e8c3027f3828bd1b4cc6cf5d12428c85b56b3e5c7fe2eb02d84840ced5cd2ed6",
262 | "b51c3f86231445a7a6e3f110ea7625cd5c9e625931c09cb1539d1461473aa63f",
263 | "03d40c382fbaf06157a167c87ab16aa96dd176ac5aac155c1532670c83ece092",
264 | "bb35364ad9976442218c45a342e13325c762e2860474208db6cc483c497a1a9b",
265 | "7de8639b9b6cc0241ea7e7efc845febfd37f5bdc3c8ec83700597d476f206714",
266 | "5b87e88536acc987599a91368fdd38f59384d32cbbedb183416211afb7497ffb",
267 | "816459ebcb6fc91d92eea176c551f3b1154e447b361babf4834fdf6c6fd28297",
268 | "451eda7200779c0fddc19e9e7155fa4d7e01c794fbf385caf534fa794ebf2772",
269 | "e081a6537fe9f86f543b11e8801aa657ed58900d33ed958f4d129de1ae691ba8",
270 | "f5621eff53a261b5e5e3c0368e259c89ad4eb5e79de5ba3ca246b01bcd9f4d37",
271 | "6f0259397eb7306fc5ff525940a368868ef9d51128a3d212ba5afe548ad3a5ce",
272 | "b15737a1d149878e4a8f37f37c26cb347997b08af59e2974a06d71b163ae2a5e",
273 | "5ab885bbf4d30d99b0cd4c393236dfd5d3c19aad50e81f79a69c63ef80f2ac2c",
274 | "30dceb0476379e7d9b3ed22039e96e63d2029f433d7a46cc1c1e2d5b2254f805",
275 | "ee9adbec36a9c63c72599295c75dd276db6a24838c817aa8be015d16d6a9ff38",
276 | "a00e16eeaa6cd23f2d5f4f3ab3acadc7fae3e2041f067fb1d117a0eddde6c278"
277 | ],
278 | "outputTag": "e93e919b787678fb5ec232273b2f6bd35d54b4991932c729dfbbd3e258d8a4e9",
279 | "maxIterations": 25600,
280 | "seed": "129c5110fbe1211c74ee287d152cd57bfeafb40984477cb85bdb9690b55dcca0",
281 | "expected": {
282 | "inputIndex": 1,
283 | "proof": "000102000000020000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
284 | }
285 | }
286 | ],
287 | "generate": [
288 | {
289 | "ephemeralInputTags": [
290 | "0ae9768886dc720aeb53fa7cca5149758892a8babd1c655a0d04b708d57edac0f7",
291 | "0ad5e6e3751e981df11c3c911933bdd4783e4cf534cabd4a36abf9e23720b88e47",
292 | "0b3c41096f1095f7e5a40c7e1923194ffa2f1615cb55ae57c2c9e9eca79148d359",
293 | "0a3b39aac328b72337ebca3c0c01ff9e9fcdfa6e42756301194cac3239924e01e3",
294 | "0b06427d4115f210f7c30a8ebe38ba608ea3510bb4027953996079bd60265c3f83",
295 | "0a7faa327c76b9cfd76c382508715fc81043b1b0a382d149c0c638022709f38446",
296 | "0a5c4285803036b398fd6e9bdde0d2a509669a01d18dc4105eb13c9c0956466f57",
297 | "0b92cb7d7eb8f09fe4d9521da3985e70331bdc2ac5fedb99230f55649234512e2d",
298 | "0bc5a67369bf2b25fe8e21242517fd14cf0d8e263d91a62fc7fcb5c1ff1d6e7cab",
299 | "0beaf64d1c4dff9efa23f4d26d640e3bc814f5384647d600f185c16f47c345cb3c"
300 | ],
301 | "ephemeralOutputTag": "0afcd623ef727831f32cbb831593c1ac45c415b7d85a6afe5817748bb81249daab",
302 | "inputIndex": 0,
303 | "inputBlindingKey": "aaca28406f0a6b5f4d3c2662835e58acf55579b40a06ca4908bcdd1c2f708b01",
304 | "outputBlindingKey": "9cf03779dd4497f3af2960a8934a6963475cabbe95cd5b80152e80da18fc689b",
305 | "proof": "0a0009020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
306 | "expectedProof": "0a000902942d0787183297f3bd5886f04d2dad06a2b2c262aae163fbe9786c3c03f0397dad7154154d8cc0f842607ec74cff837b18e86717c798c908b3aed5610ac9f965af3a8e3fd6638163a63cffdd41148eb26dd92082ae90e1e960c3d90e444f2ef82114b9efd2e9a2709268051f44b39aa1e3dd3db12a5c0c67e0263fe6a5f7e794"
307 | },
308 | {
309 | "ephemeralInputTags": [
310 | "0b51f2a80bd859c5afcbdbbf6f9da497daab7adc98ba5559fbb00d12c9bcab48cf",
311 | "0a07c10fc8a632f30f85fe7ac51f27e7130f760f47fbe7c4adb4466b64c8f84016",
312 | "0bfff628fb3fd67f95920ebf440e997a5dce6c64341ed57e09ca051ccca4ab2a6a",
313 | "0ac03679e8fb7d2f3c6bc71ed8a4495a03870f51fae2d8a56917a4f7772a2d51ca",
314 | "0bf8222d938673d969605cbd7c02a3dc846a70bdf3da7c833e7c3e52d59b146fb0",
315 | "0a99cbfbc22a171b23e08bf016ca345e9e7372ad507681451e5320fd5a2b2fe031",
316 | "0b689df4967b887e206f2d817800cc7fd2a5094c6d1ea105d94a46e29d61394b7c",
317 | "0aa327ec07f3de6033a7f7310b55b51b774169ceda05dd6f46c68cd8d47183099c",
318 | "0b067b7959720586aebae6246dd4845a71c2c6510b2b5c7db2cb15db930fb63e30",
319 | "0b1b7c5853373d2ef1236d8bb341fae729902b2e2531e7524998f6953fc19d8639"
320 | ],
321 | "ephemeralOutputTag": "0a654c296a0d252bc8285f659a8f8494cf698e95915c29f4100a39c9cbb121a952",
322 | "inputIndex": 0,
323 | "inputBlindingKey": "1cb6e686d5f767dc18eccd35e30d439d5c49594e3b9f9068864f0d777e6f60d4",
324 | "outputBlindingKey": "fe6b9d624c6364a9a84118e530a002abb4086b1215f34df3db4a501acaaa034e",
325 | "proof": "0a0003020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
326 | "expectedProof": "0a0003029f0a3483ff8e63e5f41019d3ff0c7152721f8f3f90a6c61aeddd08cc6cd1ef83410e9f1eb76b58e417fa6c8f8b2767eb272c13c44db97ad7e756f35f166dead3e0aae16e891f1b78c05a8fe4964cd040e9b6a12c1055865410f1c51c8a5d8c967cc69a308baab0e7a0022b0f8a2d16f0e898993d59cf1b1e379c581740a4d000"
327 | }
328 | ],
329 | "verify": [
330 | {
331 | "proof": "0a00110293cdf66605dcc2374f24b687ba1b45ca81719fed8e2bdabb0da40a22e5866bb67a152709f526085f4aa314661110d666bf06d795475ec7f24c9aaca0924146d62560b90290bddcc38bdbf8a08fdb46ab55cff7be00fc26fe22038f2de39e6cc4b7ba9b6e7668f06dccf7f3040a01e77651ba0c097a56d4c52f964affb12e8f11",
332 | "ephemeralInputTags": [
333 | "0b28383e00f962211a0cc390d461ed24cf881a5f6eae6118f601946bc846656d98",
334 | "0a87361563fc4fbb6f944f94f68d20e297114981a8c635dc2ab95d5668b68e3adc",
335 | "0b6df3473d6c57373b78611d3bd94f6cb4400eda2248c8014f49e7233de56feb03",
336 | "0a8cf4ece91fd2cb696207281ba3489046cc2c2fa5b70fddf00bc2b478e6924687",
337 | "0bcbb0f02c0136b22c6fe8a6ba9fc087476d673af8ac421b409e533faaec666594",
338 | "0ba370ef97373ed828a89f963d40c145ee1dfa8adea109a94a465c36783a2af99c",
339 | "0a980958cba6e351311f4162eafde8e4f1b373b18f0b2e51ee7011fea118c8d77e",
340 | "0a34e2f46bfb685ebbdaecdba8b9421722599efc09f2c7a24f19da835db08c490f",
341 | "0ab418daf979d5a5e18a84411b4f52ebe5af19d3f7b733e321e0690b287fc1bd70",
342 | "0b2dba305b4359f84333dd2d4fa2b8dbe651e19dd9c97325d6c648d85cad81c682"
343 | ],
344 | "ephemeralOutputTag": "0be16fc6f89ea1ff600b7fd8862669170de84b7351b01dc8230c395a276ae2d9ad",
345 | "expected": true
346 | },
347 | {
348 | "proof": "0a00a1000a00ef225b533b9137448b7caa91213264daa6e712540247675e2604fb0a72fde47ea16684b55cdcab592d03f8a57ab1c04a4db8ffb2b23862e1fa5c32f66dda62258c5f59b79dcb618740a478a2df60a3347729e14440c9d6378f013ee73c44fe399b7efe404a8dedd71181d844fb931f5d1ca58f2121e9f1b5d879ad7788e0",
349 | "ephemeralInputTags": [
350 | "0ade93aff2e09962f8337ea45530e37ab52ca796aeb21b98bc6ea74a0fdd5be928",
351 | "0afbf3061046200e9c85086f95d76b3c93cb117b277dd699ccff343677d554633c",
352 | "0addb41861850e38363e57fe47646638e886e529e710d429e9d1211ee36bb58fcd",
353 | "0a71c5d7d8b6070cab9e2bad4f278223a9360b923c9e3bef0bde8197c906f3787e",
354 | "0b9940412262501d0dc86c850ab6b86a34902812718f66e063e7c9f8e3911a9dd0",
355 | "0ae72085056afda5a0b12e34472787be284a65ee453e9702c7266561504c4e1806",
356 | "0bafd1d026d749df4a19f99f380ac106465ca354312837137499b5e177d9825c4a",
357 | "0a773789d10afed7e52861a896b84426c242afa83eba477477c205ddb94a8d5630",
358 | "0b5c01d8e5ff793100d2819cab5fd8d085be24a7bdf45e69933df727b0b54b90ff",
359 | "0a7b83d5032e60bbbf9e23e8be365f7b07b1c5b5800927461a053109313e188f7c"
360 | ],
361 | "ephemeralOutputTag": "0a70dbf7ee1ade055bd51c5b644eea7b597c1a05b650dddd8e0a83fc2c21adf957",
362 | "expected": true
363 | },
364 | {
365 | "proof": "0a00c1001c3a89d24140f4d1beaca89d61e57ebd7754c0f7b376b6a2c1bf963071f65175388162b2b9b61107d4f1c820695a4956a2561fad63b8f1e4ec5182a4b90b71b9f8a3a223b79e8ba46451eb024152791dbffb2507992fc10c4a9787c056e0f8676e763db552c760cc0849961a551f8c39761f2b2343a3f16a93032368d8d04895",
366 | "ephemeralInputTags": [
367 | "0b9e225490532dcca50a825a5688e09c03ab47ec0a8c36900837148187508da3fa",
368 | "0a8aae3b003955447f58853524c9abd1993fc917e409283e2583ecc60121871115",
369 | "0a7599a0599f6b223e5579b7a81ce23a0247874dce709a6f48251bf55753616239",
370 | "0b9779b5f74f53303a23b9db780478352a0b295b6240e0d0f6879a6cc0bb54ce63",
371 | "0be2d2eb492a2976d84d95f4f3cd12fa6ce8f068d2f2b565a44530fe511d9983ab",
372 | "0b03350151d33182cb8cb948f153ea41307fd3b9e716c7503638ed79663ad78bf4",
373 | "0acd16bf9c4c38945fb64591d601ac71c77215d7ddde4fb4bf02ba83fc8722ef84",
374 | "0abe9cb14225029ed3a069e9b4e6656f2edc47e63679c81075b68016f158812ea4",
375 | "0b1c05d516956259fe0b5f465f4e9dd517c0b35b1c571a5b7faf0750c9004014ba",
376 | "0b71db9c065971912ae0d7b9c4005fb34de774dfe285dbcd7db3a3dd12c66b6e77"
377 | ],
378 | "ephemeralOutputTag": "0bf5bafc9ff87b7e685b996be1961684deff9b9b888cea291852c419d8ddc06212",
379 | "expected": true
380 | }
381 | ]
382 | }
--------------------------------------------------------------------------------