Skip to content

Commit

Permalink
remove multiple sign and verify functions, leave only 1 more universal
Browse files Browse the repository at this point in the history
  • Loading branch information
borislav-itskov committed Sep 18, 2023
1 parent d0abf62 commit 7474b82
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 224 deletions.
210 changes: 80 additions & 130 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,39 +66,66 @@ const challenge = (R: Buffer, msgHash: string, publicKey: Buffer): Buffer => {
))
}

/**
* Sign the given hash by the private key
*
* @param Buffer privateKey
* @param string hash
* @returns InternalSignature
*/
const internalSign = (privateKey: Buffer, hash: string): InternalSignature => {
const localPk = Buffer.from(privateKey)
const publicKey = Buffer.from(secp256k1.publicKeyCreate(localPk))
export const _generateL = (publicKeys: Array<Buffer>) => {
return ethers.utils.keccak256(_concatTypedArrays(publicKeys.sort(Buffer.compare)))
}

// R = G * k
var k = ethers.utils.randomBytes(32)
var R = Buffer.from(secp256k1.publicKeyCreate(k))
export const _concatTypedArrays = (publicKeys: Buffer[]): Buffer => {
const c: Buffer = Buffer.alloc(publicKeys.reduce((partialSum, publicKey) => partialSum + publicKey.length, 0))
publicKeys.map((publicKey, index) => c.set(publicKey, (index * publicKey.length)))
return Buffer.from(c.buffer)
}

// e = h(address(R) || compressed pubkey || m)
var e = challenge(R, hash, publicKey)

// xe = x * e
var xe = secp256k1.privateKeyTweakMul(localPk, e)
export const _aCoefficient = (publicKey: Buffer, L: string): Buffer => {
return Buffer.from(ethers.utils.arrayify(ethers.utils.solidityKeccak256(
['bytes', 'bytes'],
[L, publicKey]
)))
}

// s = k + xe mod(n)
var s = Buffer.from(secp256k1.privateKeyTweakAdd(k, xe))
s = bigi.fromBuffer(s).mod(n).toBuffer(32)
export const generateRandomKeys = () => {
let privKeyBytes: Buffer
do {
privKeyBytes = Buffer.from(ethers.utils.randomBytes(32))
} while (!secp256k1.privateKeyVerify(privKeyBytes))

const pubKey = Buffer.from(secp256k1.publicKeyCreate(privKeyBytes))

const data = {
publicKey: pubKey,
privateKey: privKeyBytes,
}

return new KeyPair(data)
}

export const _hashPrivateKey = (privateKey: Buffer): string => {
return ethers.utils.keccak256(privateKey)
}

export const _generatePublicNonces = (privateKey: Buffer): {
privateNonceData: Pick<InternalNoncePairs, 'k' | 'kTwo'>,
publicNonceData: InternalPublicNonces,
hash: string,
} => {
const hash = _hashPrivateKey(privateKey)
const nonce = _generateNonce()

return {
finalPublicNonce: R,
challenge: e,
signature: s
hash,
privateNonceData: {
k: nonce.k,
kTwo: nonce.kTwo,
},
publicNonceData: {
kPublic: nonce.kPublic,
kTwoPublic: nonce.kTwoPublic,
}
}
}

const internalMultiSigSign = (nonces: InternalNonces, combinedPublicKey: Buffer, privateKey: Buffer, hash: string, publicKeys: Buffer[], publicNonces: InternalPublicNonces[]): InternalSignature => {
export const _multiSigSign = (nonces: InternalNonces, combinedPublicKey: Buffer, privateKey: Buffer, hash: string, publicKeys: Buffer[], publicNonces: InternalPublicNonces[]): InternalSignature => {
if (publicKeys.length < 2) {
throw Error('At least 2 public keys should be provided')
}
Expand Down Expand Up @@ -155,16 +182,16 @@ const internalMultiSigSign = (nonces: InternalNonces, combinedPublicKey: Buffer,
}
}

/**
* Verify a signature for the given hash, public nonce and public key
*
* @param Buffer s
* @param string hash
* @param Buffer R
* @param Buffer publicKey
* @returns boolean
*/
const internalVerify = (s: Buffer, hash: string, R: Buffer, publicKey: Buffer): boolean => {
export const _sumSigs = (signatures: Buffer[]): Buffer => {
let combined = bigi.fromBuffer(signatures[0])
signatures.shift()
signatures.forEach(sig => {
combined = combined.add(bigi.fromBuffer(sig))
})
return combined.mod(n).toBuffer(32)
}

export const _verify = (s: Buffer, hash: string, R: Buffer, publicKey: Buffer): boolean => {
const eC = challenge(R, hash, publicKey)
const sG = generatorPoint.mul(ethers.utils.arrayify(s))
const P = ec.keyFromPublic(publicKey).getPublic()
Expand All @@ -175,109 +202,32 @@ const internalVerify = (s: Buffer, hash: string, R: Buffer, publicKey: Buffer):
return sG.eq(RplusPe)
}

export const _generateL = (publicKeys: Array<Buffer>) => {
return ethers.utils.keccak256(_concatTypedArrays(publicKeys.sort(Buffer.compare)))
}

export const _concatTypedArrays = (publicKeys: Buffer[]): Buffer => {
const c: Buffer = Buffer.alloc(publicKeys.reduce((partialSum, publicKey) => partialSum + publicKey.length, 0))
publicKeys.map((publicKey, index) => c.set(publicKey, (index * publicKey.length)))
return Buffer.from(c.buffer)
}


export const _aCoefficient = (publicKey: Buffer, L: string): Buffer => {
return Buffer.from(ethers.utils.arrayify(ethers.utils.solidityKeccak256(
['bytes', 'bytes'],
[L, publicKey]
)))
export const _generatePk = (combinedPublicKey: Buffer): string => {
const px = ethers.utils.hexlify(combinedPublicKey.subarray(1,33))
return '0x' + px.slice(px.length - 40, px.length)
}

export const generateRandomKeys = () => {
let privKeyBytes: Buffer
do {
privKeyBytes = Buffer.from(ethers.utils.randomBytes(32))
} while (!secp256k1.privateKeyVerify(privKeyBytes))

const pubKey = Buffer.from(secp256k1.publicKeyCreate(privKeyBytes))
export const _sign = (privateKey: Buffer, hash: string): InternalSignature => {
const localPk = Buffer.from(privateKey)
const publicKey = Buffer.from(secp256k1.publicKeyCreate(localPk))

const data = {
publicKey: pubKey,
privateKey: privKeyBytes,
}
// R = G * k
var k = ethers.utils.randomBytes(32)
var R = Buffer.from(secp256k1.publicKeyCreate(k))

return new KeyPair(data)
}
// e = h(address(R) || compressed pubkey || m)
var e = challenge(R, hash, publicKey)

export const _hashPrivateKey = (privateKey: Buffer): string => {
return ethers.utils.keccak256(privateKey)
}
// xe = x * e
var xe = secp256k1.privateKeyTweakMul(localPk, e)

export const _generatePublicNonces = (privateKey: Buffer): {
privateNonceData: Pick<InternalNoncePairs, 'k' | 'kTwo'>,
publicNonceData: InternalPublicNonces,
hash: string,
} => {
const hash = _hashPrivateKey(privateKey)
const nonce = _generateNonce()
// s = k + xe mod(n)
var s = Buffer.from(secp256k1.privateKeyTweakAdd(k, xe))
s = bigi.fromBuffer(s).mod(n).toBuffer(32)

return {
hash,
privateNonceData: {
k: nonce.k,
kTwo: nonce.kTwo,
},
publicNonceData: {
kPublic: nonce.kPublic,
kTwoPublic: nonce.kTwoPublic,
}
finalPublicNonce: R,
challenge: e,
signature: s
}
}

export const _multiSigSign = (nonces: InternalNonces, combinedPublicKey: Buffer, privateKey: Buffer, msg: string, publicKeys: Buffer[], publicNonces: InternalPublicNonces[], hashFn: Function|null = null): InternalSignature => {
const hashMsg = hashFn ? hashFn : _hashMessage
const hash = hashMsg(msg)
return internalMultiSigSign(nonces, combinedPublicKey, privateKey, hash, publicKeys, publicNonces)
}

export const _multiSigSignHash = (nonces: InternalNonces, combinedPublicKey: Buffer, privateKey: Buffer, hash: string, publicKeys: Buffer[], publicNonces: InternalPublicNonces[]): InternalSignature => {
return internalMultiSigSign(nonces, combinedPublicKey, privateKey, hash, publicKeys, publicNonces)
}

export const _sumSigs = (signatures: Buffer[]): Buffer => {
let combined = bigi.fromBuffer(signatures[0])
signatures.shift()
signatures.forEach(sig => {
combined = combined.add(bigi.fromBuffer(sig))
})
return combined.mod(n).toBuffer(32)
}

export const _hashMessage = (message: string): string => {
return ethers.utils.solidityKeccak256(['string'], [message])
}

export const _verify = (s: Buffer, msg: string, R: Buffer, publicKey: Buffer, hashFn: Function|null = null): boolean => {
const hashMsg = hashFn ? hashFn : _hashMessage
const hash = hashMsg(msg)
return internalVerify(s, hash, R, publicKey)
}

export const _verifyHash = (s: Buffer, hash: string, R: Buffer, publicKey: Buffer): boolean => {
return internalVerify(s, hash, R, publicKey)
}

export const _generatePk = (combinedPublicKey: Buffer): string => {
const px = ethers.utils.hexlify(combinedPublicKey.subarray(1,33))
return '0x' + px.slice(px.length - 40, px.length)
}

export const _sign = (privateKey: Buffer, msg: string, hashFn: Function|null = null): InternalSignature => {
const hashMsg = hashFn ? hashFn : _hashMessage
const hash = hashMsg(msg)
return internalSign(privateKey, hash)
}

export const _signHash = (privateKey: Buffer, hash: string): InternalSignature => {
return internalSign(privateKey, hash)
}
48 changes: 7 additions & 41 deletions src/schnorrkel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import secp256k1 from 'secp256k1'

import { Key, Nonces, PublicNonces, Signature, NoncePairs } from './types'

import { _generateL, _aCoefficient, _generatePublicNonces, _multiSigSign, _hashPrivateKey, _sumSigs, _verify, _generatePk, _sign, _signHash, _verifyHash, _multiSigSignHash } from './core'
import { _generateL, _aCoefficient, _generatePublicNonces, _multiSigSign, _hashPrivateKey, _sumSigs, _verify, _generatePk, _sign } from './core'
import { InternalNonces, InternalPublicNonces, InternalSignature } from './core/types'
import { Challenge, FinalPublicNonce, SignatureOutput } from './types/signature'

Expand Down Expand Up @@ -112,46 +112,22 @@ class Schnorrkel {
return hash in this.nonces
}

multiSigSign(privateKey: Key, msg: string, publicKeys: Key[], publicNonces: PublicNonces[], hashFn: Function|null = null): SignatureOutput {
multiSigSign(privateKey: Key, hash: string, publicKeys: Key[], publicNonces: PublicNonces[]): SignatureOutput {
const combinedPublicKey = Schnorrkel.getCombinedPublicKey(publicKeys)
const mappedPublicNonce = this.getMappedPublicNonces(publicNonces)
const mappedNonces = this.getMappedNonces()

const musigData = _multiSigSign(mappedNonces, combinedPublicKey.buffer, privateKey.buffer, msg, publicKeys.map(key => key.buffer), mappedPublicNonce, hashFn)
const musigData = _multiSigSign(mappedNonces, combinedPublicKey.buffer, privateKey.buffer, hash, publicKeys.map(key => key.buffer), mappedPublicNonce)

// absolutely crucial to delete the nonces once a signature has been crafted with them.
// absolutely crucial to delete the nonces once a signature has been crafted.
// nonce reuse will lead to private key leakage!
this.clearNonces(privateKey)

return this.getMultisigOutput(musigData)
}

multiSigSignHash(privateKey: Key, hash: string, publicKeys: Key[], publicNonces: PublicNonces[]): SignatureOutput {
const combinedPublicKey = Schnorrkel.getCombinedPublicKey(publicKeys)
const mappedPublicNonce = this.getMappedPublicNonces(publicNonces)
const mappedNonces = this.getMappedNonces()

const musigData = _multiSigSignHash(mappedNonces, combinedPublicKey.buffer, privateKey.buffer, hash, publicKeys.map(key => key.buffer), mappedPublicNonce)

// absolutely crucial to delete the nonces once a signature has been crafted with them.
// nonce reuse will lead to private key leakage!
this.clearNonces(privateKey)

return this.getMultisigOutput(musigData)
}

static sign(privateKey: Key, msg: string, hashFn: Function|null = null): SignatureOutput {
const output = _sign(privateKey.buffer, msg, hashFn)

return {
signature: new Signature(Buffer.from(output.signature)),
finalPublicNonce: new FinalPublicNonce(Buffer.from(output.finalPublicNonce)),
challenge: new Challenge(Buffer.from(output.challenge)),
}
}

static signHash(privateKey: Key, hash: string): SignatureOutput {
const output = _signHash(privateKey.buffer, hash)
static sign(privateKey: Key, hash: string): SignatureOutput {
const output = _sign(privateKey.buffer, hash)

return {
signature: new Signature(Buffer.from(output.signature)),
Expand All @@ -167,22 +143,12 @@ class Schnorrkel {
}

static verify(
signature: Signature,
msg: string,
finalPublicNonce: FinalPublicNonce,
publicKey: Key,
hashFn: Function|null = null
): boolean {
return _verify(signature.buffer, msg, finalPublicNonce.buffer, publicKey.buffer, hashFn)
}

static verifyHash(
signature: Signature,
hash: string,
finalPublicNonce: FinalPublicNonce,
publicKey: Key
): boolean {
return _verifyHash(signature.buffer, hash, finalPublicNonce.buffer, publicKey.buffer)
return _verify(signature.buffer, hash, finalPublicNonce.buffer, publicKey.buffer)
}
}

Expand Down
4 changes: 2 additions & 2 deletions tests/schnorrkel/multiSigSign.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('testing multiSigSign', () => {
const publicKeys = [keyPairOne.publicKey, keyPairTwo.publicKey]

const msg = 'test message'
const signature = schnorrkelOne.multiSigSign(keyPairOne.privateKey, msg, publicKeys, publicNonces)
const signature = schnorrkelOne.multiSigSign(keyPairOne.privateKey, ethers.utils.solidityKeccak256(['string'], [msg]), publicKeys, publicNonces)

expect(signature).toBeDefined()
expect(signature.finalPublicNonce.buffer).toHaveLength(33)
Expand Down Expand Up @@ -63,7 +63,7 @@ describe('testing multiSigSign', () => {

const msg = 'test message'
const hash = ethers.utils.solidityKeccak256(['string'], [msg])
const signature = schnorrkelOne.multiSigSignHash(keyPairOne.privateKey, hash, publicKeys, publicNonces)
const signature = schnorrkelOne.multiSigSign(keyPairOne.privateKey, hash, publicKeys, publicNonces)

expect(signature).toBeDefined()
expect(signature.finalPublicNonce.buffer).toHaveLength(33)
Expand Down
Loading

0 comments on commit 7474b82

Please sign in to comment.