├── .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 | [![Build Status](https://travis-ci.org/vulpemventures/secp256k1-zkp.png?branch=master)](https://travis-ci.org/vulpemventures/secp256k1-zkp) 2 | [![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](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 | } --------------------------------------------------------------------------------