From 951713b2477a5993e51164f64026421df87733ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= <4456749+brickpop@users.noreply.github.com> Date: Fri, 5 Feb 2021 19:55:03 +0100 Subject: [PATCH] Allowing to encode and decode hex signatures (#2) * Allowing to encode and decode hex signatures * Minor arrangement --- src/index.ts | 45 +++++++++++++++++++++++++++++++++++++-------- test/index.test.ts | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index a1578a1..b4362c8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,14 +37,6 @@ export function decodePoint(hexPoint: string): Point { return secp256k1.keyFromPublic(Buffer.from(hexPoint, "hex")).getPublic() } -function random(bytes: number) { - let k: BigNumber - do { - k = new BigNumber(randomBytes(bytes)) - } while (k.toString() == "0" && k.gcd(n).toString() != "1") - return k -} - export function newKeyPair() { const sk = random(32) return { sk: sk, pk: G.mul(sk) } @@ -121,3 +113,40 @@ export function verify(m: BigNumber, s: UnblindedSignature, q: Point) { } return false } + +export function signatureToHex(signature: UnblindedSignature): string { + if (!signature || !signature.f || !signature.s) throw new Error("The signature is empty") + const { f, s } = signature + + // hex(swapEndiannes(s) ) + hex(f) + const hexPaddedS = zeroPad(s.toBuffer().toString("hex"), 32) + const flippedHexS = Buffer.from(hexPaddedS, "hex").reverse().toString("hex") + + return zeroPad(flippedHexS, 32) + zeroPad(f.encode("hex", false).substr(2), 64) // strip 04 +} + +export function signatureFromHex(hexSignature: string): UnblindedSignature { + if (!hexSignature || hexSignature.length != 192) throw new Error("Invalid hex signature (96 bytes expected)") + + const s = new BigNumber(Buffer.from(hexSignature.substr(0, 64), "hex").reverse()) + const f = decodePoint("04" + hexSignature.substr(64)) + return { s, f } +} + +// HELPERS + +function random(bytes: number) { + let k: BigNumber + do { + k = new BigNumber(randomBytes(bytes)) + } while (k.toString() == "0" && k.gcd(n).toString() != "1") + return k +} + +function zeroPad(hexString: string, byteLength: number) { + if (hexString.length > (byteLength * 2)) throw new Error("Out of bounds") + while (hexString.length < (byteLength * 2)) { + hexString = "0" + hexString + } + return hexString +} diff --git a/test/index.test.ts b/test/index.test.ts index df67dd5..5feed73 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -13,7 +13,9 @@ import { verify, hashBigNumber, Point, - BigNumber + BigNumber, + signatureToHex, + signatureFromHex } from "../src/index" describe("keccak256", function () { @@ -53,3 +55,40 @@ describe("blind signatures", function () { assert(verified) }) }) + +describe("encoding", () => { + it("should encode and decode signatures", () => { + const { sk } = newKeyPair() + const { k, signerR } = newRequestParameters() + const msg = messageToBigNumber("test") + const { mBlinded, userSecretData } = blind(msg, signerR) + const sBlind = blindSign(sk, mBlinded, k) + const sig = unblind(sBlind, userSecretData) + + const hexSignature = signatureToHex(sig) + assert.strictEqual(hexSignature.length, 192) + + const decodedSig = signatureFromHex(hexSignature) + assert.strictEqual(decodedSig.f.encode("hex", false).length, 130) + + assert.strictEqual(signatureToHex(signatureFromHex(hexSignature)), hexSignature) + + // explicit values + const hexSignature2 = "089a89f07bd41560454b1640fd30e51ee088d1be8355275a88e38a38f2e7a3af9e80fcc14af9c47c8066c6726e7d3cac9370494d5c67936b2978d6cecf5a4d21bf3ef00b060c47ba874c0764d662eff2d0e9daa8ba766f4aa6a2be8ec3d37523" + + const decodedSig2 = signatureFromHex(hexSignature2) + assert.strictEqual(decodedSig2.s.toString("hex"), "afa3e7f2388ae3885a275583bed188e01ee530fd40164b456015d47bf0899a08") + assert.strictEqual(decodedSig2.f.encode("hex", false).substr(2), "9e80fcc14af9c47c8066c6726e7d3cac9370494d5c67936b2978d6cecf5a4d21bf3ef00b060c47ba874c0764d662eff2d0e9daa8ba766f4aa6a2be8ec3d37523") + + assert.strictEqual(signatureToHex(signatureFromHex(hexSignature2)), hexSignature2) + + // swapEndianness(s) starting with 0 + const hexSignature3 = "089a89f07bd41560454b1640fd30e51ee088d1be8355275a88e38a38f2e7a30f9e80fcc14af9c47c8066c6726e7d3cac9370494d5c67936b2978d6cecf5a4d21bf3ef00b060c47ba874c0764d662eff2d0e9daa8ba766f4aa6a2be8ec3d37523" + + const decodedSig3 = signatureFromHex(hexSignature3) + assert.strictEqual(decodedSig3.s.toString("hex"), /* 0 */ "fa3e7f2388ae3885a275583bed188e01ee530fd40164b456015d47bf0899a08") + assert.strictEqual(decodedSig3.f.encode("hex", false).substr(2), "9e80fcc14af9c47c8066c6726e7d3cac9370494d5c67936b2978d6cecf5a4d21bf3ef00b060c47ba874c0764d662eff2d0e9daa8ba766f4aa6a2be8ec3d37523") + + assert.strictEqual(signatureToHex(signatureFromHex(hexSignature3)), hexSignature3) + }) +})