Skip to content

Commit

Permalink
Merge pull request #52 from bitcoinjs/feature/schnorr
Browse files Browse the repository at this point in the history
Add schnorr signing and verification
junderw authored Nov 11, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents f6b9b5b + e9806c0 commit 3e3619f
Showing 7 changed files with 215 additions and 2 deletions.
14 changes: 14 additions & 0 deletions src/bip32.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const crypto = require("./crypto");
const testecc_1 = require("./testecc");
const bs58check = require('bs58check');
const typeforce = require('typeforce');
const wif = require('wif');
function default_1(ecc) {
testecc_1.testEcc(ecc);
const UINT256_TYPE = typeforce.BufferN(32);
const NETWORK_TYPE = typeforce.compile({
wif: typeforce.UInt8,
@@ -209,9 +211,21 @@ function default_1(ecc) {
return sig;
}
}
signSchnorr(hash) {
if (!this.privateKey)
throw new Error('Missing private key');
if (!ecc.signSchnorr)
throw new Error('signSchnorr not supported by ecc library');
return Buffer.from(ecc.signSchnorr(hash, this.privateKey));
}
verify(hash, signature) {
return ecc.verify(hash, this.publicKey, signature);
}
verifySchnorr(hash, signature) {
if (!ecc.verifySchnorr)
throw new Error('verifySchnorr not supported by ecc library');
return ecc.verifySchnorr(hash, this.publicKey.subarray(1, 33), signature);
}
}
function fromBase58(inString, network) {
const buffer = bs58check.decode(inString);
32 changes: 32 additions & 0 deletions src/testecc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const h = (hex) => Buffer.from(hex, 'hex');
function testEcc(ecc) {
assert(ecc.isPoint(h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')));
assert(!ecc.isPoint(h('030000000000000000000000000000000000000000000000000000000000000005')));
assert(ecc.isPrivate(h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')));
// order - 1
assert(ecc.isPrivate(h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140')));
// 0
assert(!ecc.isPrivate(h('0000000000000000000000000000000000000000000000000000000000000000')));
// order
assert(!ecc.isPrivate(h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141')));
// order + 1
assert(!ecc.isPrivate(h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142')));
assert(Buffer.from(ecc.pointFromScalar(h('b1121e4088a66a28f5b6b0f5844943ecd9f610196d7bb83b25214b60452c09af'))).equals(h('02b07ba9dca9523b7ef4bd97703d43d20399eb698e194704791a25ce77a400df99')));
assert(Buffer.from(ecc.pointAddScalar(h('0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), h('0000000000000000000000000000000000000000000000000000000000000003'))).equals(h('02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5')));
assert(Buffer.from(ecc.privateAdd(h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413e'), h('0000000000000000000000000000000000000000000000000000000000000002'))).equals(h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140')));
assert(Buffer.from(ecc.sign(h('5e9f0a0d593efdcf78ac923bc3313e4e7d408d574354ee2b3288c0da9fbba6ed'), h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'))).equals(h('54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5')));
assert(ecc.verify(h('5e9f0a0d593efdcf78ac923bc3313e4e7d408d574354ee2b3288c0da9fbba6ed'), h('0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), h('54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5')));
if (ecc.signSchnorr) {
assert(Buffer.from(ecc.signSchnorr(h('7e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c'), h('c90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b14e5c9'), h('c87aa53824b4d7ae2eb035a2b5bbbccc080e76cdc6d1692c4b0b62d798e6d906'))).equals(h('5831aaeed7b44bb74e5eab94ba9d4294c49bcf2a60728d8b4c200f50dd313c1bab745879a5ad954a72c45a91c3a51d3c7adea98d82f8481e0e1e03674a6f3fb7')));
}
if (ecc.verifySchnorr) {
assert(ecc.verifySchnorr(h('7e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c'), h('dd308afec5777e13121fa72b9cc1b7cc0139715309b086c960e18fd969774eb8'), h('5831aaeed7b44bb74e5eab94ba9d4294c49bcf2a60728d8b4c200f50dd313c1bab745879a5ad954a72c45a91c3a51d3c7adea98d82f8481e0e1e03674a6f3fb7')));
}
}
exports.testEcc = testEcc;
function assert(bool) {
if (!bool)
throw new Error('ecc library invalid');
}
21 changes: 19 additions & 2 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
let BIP32Creator = require('..').default
let tape = require('tape')
let fixtures = require('./fixtures/index.json')
import('tiny-secp256k1').then(ecc => BIP32Creator(ecc)).then(BIP32 => {
let ecc
import('tiny-secp256k1').then(lib => {
ecc = lib
return BIP32Creator(lib)
}).then(BIP32 => {
let LITECOIN = {
wif: 0xb0,
bip32: {
@@ -95,6 +99,15 @@ validAll.forEach((ff) => {
})
})

tape('invalid ecc library throws', (t) => {
t.throws(() => {
BIP32Creator({ isPoint: () => false })
}, /ecc library invalid/)
// Run with no schnorr and check it doesn't throw
BIP32Creator({ ...ecc, signSchnorr: null, verifySchnorr: null })
t.end()
})

tape('fromBase58 throws', (t) => {
fixtures.invalid.fromBase58.forEach((f) => {
t.throws(() => {
@@ -215,15 +228,19 @@ tape('ecdsa', (t) => {
let seed = Buffer.alloc(32, 1)
let hash = Buffer.alloc(32, 2)
let signature = Buffer.from('9636ee2fac31b795a308856b821ebe297dda7b28220fb46ea1fbbd7285977cc04c82b734956246a0f15a9698f03f546d8d96fe006c8e7bd2256ca7c8229e6f5c', 'hex')
let schnorrsig = Buffer.from('2fae8b517cb0e7302ca48a4109d1819e3d75af96bd58d297023e3058c4e98ff812fe6ae32a2b2bc4abab10f88f7fe56efbafc8a4e4fa437af78926f528b0585e', 'hex')
let signatureLowR = Buffer.from('0587a40b391b76596c257bf59565b24eaff2cc42b45caa2640902e73fb97a6e702c3402ab89348a7dae1bf171c3e172fa60353d7b01621a94cb7caca59b995db', 'hex')
let node = BIP32.fromSeed(seed)

t.plan(6)
t.plan(9)
t.equal(node.sign(hash).toString('hex'), signature.toString('hex'))
t.equal(node.sign(hash, true).toString('hex'), signatureLowR.toString('hex'))
t.equal(node.signSchnorr(hash).toString('hex'), schnorrsig.toString('hex'))
t.equal(node.verify(hash, signature), true)
t.equal(node.verify(seed, signature), false)
t.equal(node.verify(hash, signatureLowR), true)
t.equal(node.verify(seed, signatureLowR), false)
t.equal(node.verifySchnorr(hash, schnorrsig), true)
t.equal(node.verifySchnorr(seed, schnorrsig), false)
})
})
19 changes: 19 additions & 0 deletions ts-src/bip32.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as crypto from './crypto';
import { testEcc } from './testecc';
const bs58check = require('bs58check');
const typeforce = require('typeforce');
const wif = require('wif');
@@ -35,6 +36,8 @@ export interface BIP32Interface {
derivePath(path: string): BIP32Interface;
sign(hash: Buffer, lowR?: boolean): Buffer;
verify(hash: Buffer, signature: Buffer): boolean;
signSchnorr(hash: Buffer): Buffer;
verifySchnorr(hash: Buffer, signature: Buffer): boolean;
}

export interface BIP32API {
@@ -63,15 +66,18 @@ export interface TinySecp256k1Interface {
): Uint8Array | null;
privateAdd(d: Uint8Array, tweak: Uint8Array): Uint8Array | null;
sign(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array;
signSchnorr?(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array;
verify(
h: Uint8Array,
Q: Uint8Array,
signature: Uint8Array,
strict?: boolean,
): boolean;
verifySchnorr?(h: Uint8Array, Q: Uint8Array, signature: Uint8Array): boolean;
}

export default function(ecc: TinySecp256k1Interface): BIP32API {
testEcc(ecc);
const UINT256_TYPE = typeforce.BufferN(32);
const NETWORK_TYPE = typeforce.compile({
wif: typeforce.UInt8,
@@ -340,9 +346,22 @@ export default function(ecc: TinySecp256k1Interface): BIP32API {
}
}

signSchnorr(hash: Buffer): Buffer {
if (!this.privateKey) throw new Error('Missing private key');
if (!ecc.signSchnorr)
throw new Error('signSchnorr not supported by ecc library');
return Buffer.from(ecc.signSchnorr(hash, this.privateKey));
}

verify(hash: Buffer, signature: Buffer): boolean {
return ecc.verify(hash, this.publicKey, signature);
}

verifySchnorr(hash: Buffer, signature: Buffer): boolean {
if (!ecc.verifySchnorr)
throw new Error('verifySchnorr not supported by ecc library');
return ecc.verifySchnorr(hash, this.publicKey.subarray(1, 33), signature);
}
}

function fromBase58(inString: string, network?: Network): BIP32Interface {
125 changes: 125 additions & 0 deletions ts-src/testecc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { TinySecp256k1Interface } from './bip32';

const h = (hex: string): Buffer => Buffer.from(hex, 'hex');

export function testEcc(ecc: TinySecp256k1Interface): void {
assert(
ecc.isPoint(
h('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),
),
);
assert(
!ecc.isPoint(
h('030000000000000000000000000000000000000000000000000000000000000005'),
),
);
assert(
ecc.isPrivate(
h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),
),
);
// order - 1
assert(
ecc.isPrivate(
h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'),
),
);
// 0
assert(
!ecc.isPrivate(
h('0000000000000000000000000000000000000000000000000000000000000000'),
),
);
// order
assert(
!ecc.isPrivate(
h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'),
),
);
// order + 1
assert(
!ecc.isPrivate(
h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142'),
),
);
assert(
Buffer.from(
ecc.pointFromScalar(
h('b1121e4088a66a28f5b6b0f5844943ecd9f610196d7bb83b25214b60452c09af'),
)!,
).equals(
h('02b07ba9dca9523b7ef4bd97703d43d20399eb698e194704791a25ce77a400df99'),
),
);
assert(
Buffer.from(
ecc.pointAddScalar(
h('0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),
h('0000000000000000000000000000000000000000000000000000000000000003'),
)!,
).equals(
h('02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5'),
),
);
assert(
Buffer.from(
ecc.privateAdd(
h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413e'),
h('0000000000000000000000000000000000000000000000000000000000000002'),
)!,
).equals(
h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'),
),
);
assert(
Buffer.from(
ecc.sign(
h('5e9f0a0d593efdcf78ac923bc3313e4e7d408d574354ee2b3288c0da9fbba6ed'),
h('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140'),
)!,
).equals(
h(
'54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5',
),
),
);
assert(
ecc.verify(
h('5e9f0a0d593efdcf78ac923bc3313e4e7d408d574354ee2b3288c0da9fbba6ed'),
h('0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),
h(
'54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5',
),
),
);
if (ecc.signSchnorr) {
assert(
Buffer.from(
ecc.signSchnorr(
h('7e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c'),
h('c90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b14e5c9'),
h('c87aa53824b4d7ae2eb035a2b5bbbccc080e76cdc6d1692c4b0b62d798e6d906'),
)!,
).equals(
h(
'5831aaeed7b44bb74e5eab94ba9d4294c49bcf2a60728d8b4c200f50dd313c1bab745879a5ad954a72c45a91c3a51d3c7adea98d82f8481e0e1e03674a6f3fb7',
),
),
);
}
if (ecc.verifySchnorr) {
assert(
ecc.verifySchnorr(
h('7e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c'),
h('dd308afec5777e13121fa72b9cc1b7cc0139715309b086c960e18fd969774eb8'),
h(
'5831aaeed7b44bb74e5eab94ba9d4294c49bcf2a60728d8b4c200f50dd313c1bab745879a5ad954a72c45a91c3a51d3c7adea98d82f8481e0e1e03674a6f3fb7',
),
),
);
}
}

function assert(bool: boolean): void {
if (!bool) throw new Error('ecc library invalid');
}
4 changes: 4 additions & 0 deletions types/bip32.d.ts
Original file line number Diff line number Diff line change
@@ -30,6 +30,8 @@ export interface BIP32Interface {
derivePath(path: string): BIP32Interface;
sign(hash: Buffer, lowR?: boolean): Buffer;
verify(hash: Buffer, signature: Buffer): boolean;
signSchnorr(hash: Buffer): Buffer;
verifySchnorr(hash: Buffer, signature: Buffer): boolean;
}
export interface BIP32API {
fromSeed(seed: Buffer, network?: Network): BIP32Interface;
@@ -44,7 +46,9 @@ export interface TinySecp256k1Interface {
pointAddScalar(p: Uint8Array, tweak: Uint8Array, compressed?: boolean): Uint8Array | null;
privateAdd(d: Uint8Array, tweak: Uint8Array): Uint8Array | null;
sign(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array;
signSchnorr?(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array;
verify(h: Uint8Array, Q: Uint8Array, signature: Uint8Array, strict?: boolean): boolean;
verifySchnorr?(h: Uint8Array, Q: Uint8Array, signature: Uint8Array): boolean;
}
export default function (ecc: TinySecp256k1Interface): BIP32API;
export {};
2 changes: 2 additions & 0 deletions types/testecc.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { TinySecp256k1Interface } from './bip32';
export declare function testEcc(ecc: TinySecp256k1Interface): void;

0 comments on commit 3e3619f

Please sign in to comment.