Skip to content

Commit

Permalink
document the library, change variable name for easier understandind, …
Browse files Browse the repository at this point in the history
…add a helper method generateOrGetPublicNonces
  • Loading branch information
borislav-itskov committed Sep 25, 2023
1 parent d0bd26f commit 4c115bc
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 58 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Since version 2.0.0, we're moving entirely to Typescript.
* `sign()` and `multiSigSign()` return an instance of `SignatureOutput`. Each element in it has a buffer property
* instead of `e` we return `challenge` for the Schnorr Challenge. To accces its value, use `challenge.buffer`
* instead of `s` we return `signature` for the Schnorr Signature. To accces its value, use `signature.buffer`
* instead of `R` we return `finalPublicNonce` for the nonce. To accces its value, use `finalPublicNonce.buffer`
* instead of `R` we return `publicNonce` for the nonce. To accces its value, use `publicNonce.buffer`
* `getCombinedPublicKey()` returns a `Key` class. To get the actual key, use `key.buffer`
* a lot of method become static as they don't keep any state:
* `verify`
Expand All @@ -20,6 +20,7 @@ Since version 2.0.0, we're moving entirely to Typescript.
* `getCombinedAddress`

## Version 3.0 Breaking changes
* `finalPublicNonce`, `FinalPublicNonce` is replaced everywhere with `publicNonce`, `PublicNonce`. The old name just didn't make sense.
* `sign()` is the former `signHash()`. A sign function that accepts a plain-text message as an argument no longer exists.
* `multiSigSign()` is the former `multiSigSignHash()`. A sign function that accepts a plain-text message as an argument no longer exists.
* `verify()` is the former `verifyHash()`. A verification function that accepts a plain-text message as an argument no longer exists.
Expand Down Expand Up @@ -56,15 +57,15 @@ import Schnorrkel from '@borislav.itskov/schnorrkel.js'
const privateKey = new Key(Buffer.from(ethers.utils.randomBytes(32)))
const msg = 'test message'
const hash = ethers.utils.hashMessage(msg)
const {signature, finalPublicNonce, challenge} = Schnorrkel.sign(privateKey, hash)
const {signature, publicNonce, challenge} = Schnorrkel.sign(privateKey, hash)
```

Offchain verification:
We take the `signature`, `hash` and `finalPublicNonce` from the example above and do:
We take the `signature`, `hash` and `publicNonce` from the example above and do:
```js
const publicKey = Buffer.from(secp256k1.publicKeyCreate(privateKey.buffer))
// signature and finalPublicNonce come from Schnorrkel.sign
const result = Schnorrkel.verify(signature, hash, finalPublicNonce, publicKey)
// signature and publicNonce come from Schnorrkel.sign
const result = Schnorrkel.verify(signature, hash, publicNonce, publicKey)
```

Onchain verification:
Expand Down Expand Up @@ -156,7 +157,7 @@ const publicKey1: Buffer = '...'
const publicKey2: Buffer = '...'
const publicKeys = [publicKey1, publicKey2];
const combinedPublicKey = schnorrkel.getCombinedPublicKey(publicKeys)
const {signature: sigOne, challenge: e, finalPublicNonce} = signerOne.multiSignMessage(msg, publicKeys, publicNonces)
const {signature: sigOne, challenge: e, publicNonce} = signerOne.multiSignMessage(msg, publicKeys, publicNonces)
const {signature: sigTwo} = signerTwo.multiSignMessage(msg, publicKeys, publicNonces)
const sSummed = Schnorrkel.sumSigs([sigOne, sigTwo])
```
Expand All @@ -180,7 +181,7 @@ const result = await contract.isValidSignature(msgHash, sigData);
#### verify offchain

```js
const result = schnorrkel.verify(sSummed, msg, finalPublicNonce, combinedPublicKey);
const result = schnorrkel.verify(sSummed, msg, publicNonce, combinedPublicKey);
```

You can find reference to this in `tests/schnorrkel/onchainMultiSign.test.ts` in this repository.
Expand Down
62 changes: 55 additions & 7 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import { KeyPair } from '../types'

const curve = ecurve.getCurveByName('secp256k1')

/**
* Generate two random nonces in preparation for a multisignature.
* We return along with them their public representations
*
* @returns InternalNoncePairs
*/
const generateNonce = (): InternalNoncePairs => {
const k = Buffer.from(ethers.utils.randomBytes(32))
const kTwo = Buffer.from(ethers.utils.randomBytes(32))
Expand All @@ -21,6 +27,17 @@ const generateNonce = (): InternalNoncePairs => {
}
}

/**
* Compute the b coefficient needed for multisignature signing.
* The b coefficient is needed to prevent the DL query attack
* on the public nonces, in hand allowing us to skip the nonce
* commitment round
*
* @param combinedPublicKey - the sum of the keys of the participants
* @param msgHash - the hash that's going to be signed
* @param publicNonces - the exchanged public nonces
* @returns Buffer
*/
const bCoefficient = (combinedPublicKey: Buffer, msgHash: string, publicNonces: InternalPublicNonces[]): Buffer => {
type KeyOf = keyof InternalPublicNonces
const arrayColumn = (arr: Array<InternalPublicNonces>, n: KeyOf) => arr.map(x => x[n])
Expand All @@ -45,6 +62,15 @@ const areBuffersSame = (buf1: Buffer, buf2: Buffer): boolean => {
return true
}

/**
* Compute the schnorr challenge.
* The formula is: s = k + e*d. We're computing `e` here
*
* @param R
* @param msgHash
* @param publicKey
* @returns Buffer hash(concat(public_nonce_addr, parity, x_coord, message))
*/
const challenge = (R: Buffer, msgHash: string, publicKey: Buffer): Buffer => {
// convert R to address
const R_uncomp = secp256k1.publicKeyConvert(R, false)
Expand All @@ -59,7 +85,12 @@ const challenge = (R: Buffer, msgHash: string, publicKey: Buffer): Buffer => {
))
}

export const generateRandomKeys = () => {
/**
* A helper function that creates a key pair
*
* @returns KeyPair
*/
export const generateRandomKeys = (): KeyPair => {
let privKeyBytes: Buffer
do {
privKeyBytes = Buffer.from(ethers.utils.randomBytes(32))
Expand All @@ -75,7 +106,15 @@ export const generateRandomKeys = () => {
return new KeyPair(data)
}

export const _generateL = (publicKeys: Array<Buffer>) => {
/**
* Generate a hash of all the public keys that are participating
* in the signing process. We need this to craft the `a` coefficient,
* which helps us prevent key cancelation attacks.
*
* @param publicKeys
* @returns string
*/
export const _generateL = (publicKeys: Array<Buffer>): string => {
return ethers.utils.keccak256(_concatTypedArrays(publicKeys.sort(Buffer.compare)))
}

Expand All @@ -85,7 +124,15 @@ export const _concatTypedArrays = (publicKeys: Buffer[]): Buffer => {
return Buffer.from(c.buffer)
}


/**
* Generate `a` coefficient to prevent key cancelation attacks.
* Hash commitment to all the public keys to prevent your key
* not participating in the multisignature.
*
* @param publicKey - the signer's public key
* @param L - review _generateL
* @returns Buffer hash(concat(L, own_public_key))
*/
export const _aCoefficient = (publicKey: Buffer, L: string): Buffer => {
return Buffer.from(ethers.utils.arrayify(ethers.utils.solidityKeccak256(
['bytes', 'bytes'],
Expand All @@ -107,7 +154,7 @@ export const _hashPrivateKey = (privateKey: Buffer): string => {
/**
* Generate the nonces for the next signature.
* Use the hash of the private key for a unique identifier
* TODO: maybe change this with to the public key...
*
* @param privateKey
* @returns
*/
Expand Down Expand Up @@ -183,7 +230,7 @@ export const _multiSigSign = (nonces: InternalNonces, combinedPublicKey: Buffer,
return {
signature: final,
challenge: e,
finalPublicNonce: R
publicNonce: R
}
}

Expand Down Expand Up @@ -231,7 +278,8 @@ export const _verify = (s: Buffer, hash: string, R: Buffer, publicKey: Buffer):

/**
* Take the x-coordinate of the public key and transform it
* into ethereum-like address
* into ethereum-like address.
* This is the address returned by ecrecover on-chain schnorr verification
*
* @param combinedPublicKey
* @returns address
Expand Down Expand Up @@ -267,7 +315,7 @@ export const _sign = (privateKey: Buffer, hash: string): InternalSignature => {
const s = Buffer.from(secp256k1.privateKeyTweakAdd(k, xe))

return {
finalPublicNonce: R,
publicNonce: R,
challenge: e,
signature: s
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface InternalPublicNonces {
}

export interface InternalSignature {
finalPublicNonce: Buffer, // the final public nonce
publicNonce: Buffer, // the final public nonce
challenge: Buffer, // the schnorr challenge
signature: Buffer, // the signature
}
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Schnorrkel from './schnorrkel'
export { default as UnsafeSchnorrkel } from './unsafe-schnorrkel'

export { Key, KeyPair, Signature, PublicNonces, Challenge, SignatureOutput, FinalPublicNonce } from './types'
export { Key, KeyPair, Signature, PublicNonces, Challenge, SignatureOutput, PublicNonce } from './types'

export default Schnorrkel
Loading

0 comments on commit 4c115bc

Please sign in to comment.