UNPKG

@rmf1723/schnorrkel

Version:

Schnorr signatures in the ristretto255 curve

141 lines 4.36 kB
import { Transcript } from '@rmf1723/merlin'; import { Point, Scalar } from '@rmf1723/ristretto255'; import nacl from '@rmf1723/tweetnacl'; const ctorKey = Symbol(); export { Transcript as SigningContext } from '@rmf1723/merlin'; export function randomKeySeed() { return nacl.randomBytes(32); } export function expandKey(seed) { if (seed.byteLength != 32) { throw new Error('invalid key seed length'); } const t = new Transcript('ExpandSecretKeys'); t.appendMessage('mini', seed); const key = t.challengeBytes('sk', 64); const nonce = t.challengeBytes('no', 32); return new SecretKey(ctorKey, Scalar.fromHash(key), nonce); } export default { randomKeySeed, expandKey, }; export class SecretKey { #exponent; #nonce; constructor(key, exponent, nonce) { if (key != ctorKey) { throw new Error('expand a key seed to construct a secret key'); } if (nonce.byteLength != 32) { throw new Error('invalid nonce length'); } this.#exponent = exponent; this.#nonce = new Uint8Array(nonce); } get publicKey() { return new PublicKey(Point.BASE.mul(this.#exponent)); } get exponent() { return this.#exponent.clone(); } get nonce() { return this.#nonce.slice(); } toBytes() { const buf = new Uint8Array(64); buf.set(this.#exponent.toBytes(), 0); buf.set(this.#nonce, 32); return buf; } static fromBytes(buf) { if (buf.byteLength != 64) { throw new Error('invalid secret key length'); } const exponent = new Scalar(new Uint8Array(buf.buffer, buf.byteOffset, 32)); const nonce = new Uint8Array(buf.buffer, buf.byteOffset + 32, 32); return new SecretKey(ctorKey, exponent, nonce); } sign(context) { context.appendMessage('proto-name', 'Schnorr-sig'); context.appendMessage('sign:pk', this.publicKey.point.toBytes()); const r = Scalar.fromHash(context .buildRng() .rekeyWithWitnessBytes('signing', this.#nonce) .finalize() .fillBytes(64)); const R = r.mulBase(); context.appendMessage('sign:R', R.toBytes()); const k = Scalar.fromHash(context.challengeBytes('sign:c', 64)); const s = k.mul(this.#exponent).add(r); return new Signature(R, s); } } export class PublicKey { #point; constructor(point) { this.#point = point.clone(); } get point() { return this.#point; } toBytes() { const buf = new Uint8Array(32); buf.set(this.#point.toBytes(), 0); return buf; } static fromBytes(buf) { if (buf.byteLength != 32) { throw new Error('invalid public key length'); } const point = new Point(new Uint8Array(buf.buffer, buf.byteOffset, 32)); return new PublicKey(point); } verify(context, sig) { context.appendMessage('proto-name', 'Schnorr-sig'); context.appendMessage('sign:pk', this.#point.toBytes()); context.appendMessage('sign:R', sig.R.toBytes()); const k = Scalar.fromHash(context.challengeBytes('sign:c', 64)); const R = k.mul(this.#point.mul(Scalar.ONE.negate())).add(sig.s.mulBase()); if (!R.equals(sig.R)) { throw new SignatureError(); } return ACCEPT; } } export class Signature { #R; #s; constructor(R, s) { this.#R = R; this.#s = s; } get R() { return this.#R; } get s() { return this.#s; } toBytes() { const buf = new Uint8Array(64); buf.set(this.#R.toBytes(), 0); buf.set(this.#s.toBytes(), 32); return buf; } static fromBytes(buf) { if (buf.byteLength != 64) { throw new Error('invalid signature length'); } const R = new Point(new Uint8Array(buf.buffer, buf.byteOffset, 32)); const s = new Scalar(new Uint8Array(buf.buffer, buf.byteOffset + 32, 32)); return new Signature(R, s); } } export const ACCEPT = 'ACCEPT'; export class SignatureError extends Error { constructor() { super('invalid signature'); this.name = 'SignatureError'; } } //# sourceMappingURL=schnorrkel.js.map