diff --git a/crosschain/mock/signer.js b/crosschain/mock/signer.js index fd3478d..de080b2 100644 --- a/crosschain/mock/signer.js +++ b/crosschain/mock/signer.js @@ -1,7 +1,7 @@ import evm from "../../ethereum/evm"; import { Signer as EvmSigner } from "../../ethereum/mock/signer"; -import { signMessage } from "../../mina/mina"; import { addr as minaAddr } from "../../mina/mock/signer"; +import { signMessage } from "../../mina/signer"; import { assertEq } from "../../testing/assert"; import base58 from "../../util/base58"; import hex from "../../util/hex"; diff --git a/crypto/test/integration.test.js b/crypto/test/integration.test.js index 408be31..6443198 100644 --- a/crypto/test/integration.test.js +++ b/crypto/test/integration.test.js @@ -1,6 +1,7 @@ import { expect, it, test } from "bun:test"; import evm from "../../ethereum/evm"; import { addr, signCompact, signWide } from "../../ethereum/mock/signer"; +import { signerAddress } from "../../ethereum/signer"; test("address derivation from private key", () => { expect(addr(1n)).toBe("0x7e5f4552091a69125d5dfcb7b8c2659029395bdf"); @@ -24,8 +25,8 @@ test("compact signature conversion methods", () => { it("should recover signer address correctly", () => { /** @const {bigint} */ const digest = 103n; - expect(evm.signerAddress(evm.uint256(digest), signCompact(digest, 101n))) + expect(signerAddress(evm.uint256(digest), signCompact(digest, 101n))) .toBe(addr(101n)); - expect(evm.signerAddress(digest.toString(16), signCompact(digest, 1337n))) + expect(signerAddress(digest.toString(16), signCompact(digest, 1337n))) .toBe(addr(1337n)); }); diff --git a/did/sectionSigner.js b/did/sectionSigner.js index 9c13a45..f12f330 100644 --- a/did/sectionSigner.js +++ b/did/sectionSigner.js @@ -1,6 +1,6 @@ import { ChainGroup } from "../crosschain/chains"; -import evm from "../ethereum/evm"; -import { signFields, verifyFields } from "../mina/mina"; +import { signCompact, signerAddress } from "../ethereum/signer"; +import { signFields, verifyFields } from "../mina/signer"; import { base64tenSayıya } from "../util/çevir"; import { commit } from "./commitment"; import { hash } from "./section"; @@ -59,7 +59,7 @@ const signSection = (sectionName, section, signParams) => { : signParams.commitment; section.signatureTs = signParams.signatureTs; section.secp256k1 = [ - evm.signCompact(hash(sectionName, section), signParams.privateKey) + signCompact(hash(sectionName, section), signParams.privateKey) ]; if (sectionName == "humanID") signHumanID(/** @type {!did.HumanID} */(section), signParams.privateKeyPallas); @@ -84,8 +84,7 @@ const recoverSectionSigners = (sectionName, section, chainGroup, ownerAddress) = /** @const {string} */ const h = hash(sectionName, section); /** @const {!Array} */ - const signers = section.secp256k1.map((signature) => - evm.signerAddress(h, signature)); + const signers = section.secp256k1.map((signature) => signerAddress(h, signature)); return [...new Set(signers)]; } @@ -103,9 +102,9 @@ const signDecryptedSections = (decryptedSections, signParams) => { } export { - SignParams, recoverHumanIDSigners, recoverSectionSigners, signDecryptedSections, + SignParams, signSection }; diff --git a/ethereum/eth.d.js b/ethereum/eth.d.js index 18869d2..933fea3 100644 --- a/ethereum/eth.d.js +++ b/ethereum/eth.d.js @@ -5,6 +5,13 @@ /** @const */ const eth = {}; +/** + * A length 80 hex string denoting and address without the leading 0x. + * + * @typedef {string} + */ +eth.PackedAddress; + /** * A string of length 132, starting with 0x. * diff --git a/ethereum/evm.js b/ethereum/evm.js index 1f884f0..ed9f8dd 100644 --- a/ethereum/evm.js +++ b/ethereum/evm.js @@ -3,9 +3,8 @@ * * @author KimlikDAO */ -import { Point, recoverSigner, sign } from "../crypto/secp256k1"; -import { keccak256, keccak256Uint32, keccak256Uint8 } from '../crypto/sha3'; -import { hex, uint8ArrayBEyeSayıdan } from "../util/çevir"; +import { keccak256, keccak256Uint8 } from '../crypto/sha3'; +import { hex } from "../util/çevir"; import eth from "./eth.d"; /** @@ -115,58 +114,9 @@ const personalDigest = (msg) => { return hex(keccak256Uint8(encoded)); } -/** - * @param {!Point} Q - * @return {string} address - */ -const pointToAddress = (Q) => { - /** @const {!Uint8Array} */ - const buff = new Uint8Array(64); - uint8ArrayBEyeSayıdan(buff, 32, Q.x); - uint8ArrayBEyeSayıdan(buff, 64, Q.y); - /** @const {!Uint8Array} */ - const hash = new Uint8Array( - keccak256Uint32(new Uint32Array(buff.buffer)).buffer, 12, 20); - return "0x" + hex(hash); -} - -/** - * Given a digest and a signature, recovers the signer address if the signature - * is valid; outputs an arbitrary value otherwise. - * - * @param {string} digest as a length 64 hex string - * @param {eth.CompactSignature} signature as a length 128 compact signature - * @return {string} 42 characters long EVM address - */ -const signerAddress = (digest, signature) => { - /** @const {number} */ - const highNibble = parseInt(signature[64], 16); - /** @const {boolean} */ - const yParity = highNibble >= 8; - /** @const {bigint} */ - const r = BigInt("0x" + signature.slice(0, 64)); - /** @const {bigint} */ - const s = BigInt("0x" + (yParity - ? (highNibble - 8).toString(16) + signature.slice(65) - : signature.slice(64)) - ); - return pointToAddress( - recoverSigner(BigInt("0x" + digest), r, s, yParity)); -} - -/** - * @param {string} digest - * @param {bigint} privateKey - * @return {eth.CompactSignature} - */ -const signCompact = (digest, privateKey) => { - const { r, s, yParity } = sign(BigInt("0x" + digest), privateKey); - return uint256(r) + uint256(yParity ? s + (1n << 255n) : s); -} - /** * @param {string} addr EVM adresi; 0x ile başlamalı. - * @return {string} 80 uzunluğunde hex kodlanmış adres + * @return {eth.PackedAddress} 80 uzunluğunde hex kodlanmış adres */ const packedAddress = (addr) => addr.slice(2).toLowerCase(); @@ -212,9 +162,6 @@ export default { isZero, packedAddress, personalDigest, - pointToAddress, - signerAddress, - signCompact, uint160, uint256, Uint256Max, diff --git a/ethereum/signer.js b/ethereum/signer.js new file mode 100644 index 0000000..589adf5 --- /dev/null +++ b/ethereum/signer.js @@ -0,0 +1,60 @@ +import { Point, recoverSigner, sign } from "../crypto/secp256k1"; +import { keccak256Uint32 } from "../crypto/sha3"; +import { hex, uint8ArrayBEyeSayıdan } from "../util/çevir"; +import eth from "./eth.d"; +import evm from "./evm"; + +/** + * @param {!Point} Q + * @return {string} address + */ +const pointToAddress = (Q) => { + /** @const {!Uint8Array} */ + const buff = new Uint8Array(64); + uint8ArrayBEyeSayıdan(buff, 32, Q.x); + uint8ArrayBEyeSayıdan(buff, 64, Q.y); + /** @const {!Uint8Array} */ + const hash = new Uint8Array( + keccak256Uint32(new Uint32Array(buff.buffer)).buffer, 12, 20); + return "0x" + hex(hash); +} + +/** + * Given a digest and a signature, recovers the signer address if the signature + * is valid; outputs an arbitrary value otherwise. + * + * @param {string} digest as a length 64 hex string + * @param {eth.CompactSignature} signature as a length 128 compact signature + * @return {string} 42 characters long EVM address + */ +const signerAddress = (digest, signature) => { + /** @const {number} */ + const highNibble = parseInt(signature[64], 16); + /** @const {boolean} */ + const yParity = highNibble >= 8; + /** @const {bigint} */ + const r = BigInt("0x" + signature.slice(0, 64)); + /** @const {bigint} */ + const s = BigInt("0x" + (yParity + ? (highNibble - 8).toString(16) + signature.slice(65) + : signature.slice(64)) + ); + return pointToAddress( + recoverSigner(BigInt("0x" + digest), r, s, yParity)); +} + +/** + * @param {string} digest + * @param {bigint} privateKey + * @return {eth.CompactSignature} + */ +const signCompact = (digest, privateKey) => { + const { r, s, yParity } = sign(BigInt("0x" + digest), privateKey); + return evm.uint256(r) + evm.uint256(yParity ? s + (1n << 255n) : s); +} + +export { + pointToAddress, + signCompact, + signerAddress +}; diff --git a/ethereum/test/evm/address.test.js b/ethereum/test/evm.test.js similarity index 98% rename from ethereum/test/evm/address.test.js rename to ethereum/test/evm.test.js index c51ec0c..8501281 100644 --- a/ethereum/test/evm/address.test.js +++ b/ethereum/test/evm.test.js @@ -1,5 +1,5 @@ import { describe, expect, it } from "bun:test"; -import evm from "../../evm"; +import evm from "../evm"; describe("adresDüzelt testleri", () => { it("should add checksum to lower case address", () => { diff --git a/ethereum/test/evm/sign.test.js b/ethereum/test/signer.test.js similarity index 80% rename from ethereum/test/evm/sign.test.js rename to ethereum/test/signer.test.js index 265f609..d1d81af 100644 --- a/ethereum/test/evm/sign.test.js +++ b/ethereum/test/signer.test.js @@ -1,47 +1,48 @@ import { describe, expect, it, test } from "bun:test"; -import { G } from "../../../crypto/secp256k1"; -import { keccak256 } from "../../../crypto/sha3"; -import evm from "../../evm"; +import { G } from "../../crypto/secp256k1"; +import { keccak256 } from "../../crypto/sha3"; +import evm from "../evm"; +import { pointToAddress, signCompact, signerAddress } from "../signer"; describe("Signature tests", () => { it("should recover signer address", () => { - expect(evm.signerAddress( + expect(signerAddress( evm.personalDigest("140e575468d2a8dcbcc437e0f12e37606491f1621fe71239b99b793cd590b7f4"), evm.compactSignature( "0x278f49cb66db8b104751fe3413dbf50e58288e66b625fa704b33255d06012295" + "6dc164431e9b78805da4bb590d2a8f7a340c89ed74ec04d054a3b49977ec6b4a1c"))) .toBe("0x79883d9acbc4abac6d2d216693f66fcc5a0bcbc1"); - expect(evm.signerAddress( + expect(signerAddress( evm.personalDigest("92c0aea56bd108db8f41000707872c9e5f72bd79c10d9285ddf0e32c51791948"), evm.compactSignature( "0x9400092866ff50e1d88014ca3cc3d878f6cecacc5ae8f752f3874c0b0584a90f" + "462f2947f7fbc119843e3c4319dc2da3cbb482745d42dd0e5a1cd27033270cbd1b"))) .toBe("0x79883d9acbc4abac6d2d216693f66fcc5a0bcbc1"); - expect(evm.signerAddress( + expect(signerAddress( evm.personalDigest("3e38bda94a98f1b3019dd7b48e050cbb18986724d1cfd29ceb9c8e57c7351866"), evm.compactSignature( "0x09af513d1be6760e9a2e6bfa9b2166b337c2e2f830a3b75b01f41b25804c3710" + "44065ee77b34b36fd8a8ea9bda7acf4fd5ab7bf56879a33df8293d744784cb1e1b"))) .toBe("0x79883d9acbc4abac6d2d216693f66fcc5a0bcbc1"); - expect(evm.signerAddress( + expect(signerAddress( evm.personalDigest("İmza, KimlikDAO"), evm.compactSignature( "0x6c8b90ce7867d3bab682a8d8ac47293e3b8e733b5c21843d9425da4de1fa4b4d" + "1495991af65b57164b042186c02bac62a292b3247e322aa4d1f9ab7519e87f4d1b"))) .toBe("0x79883d9acbc4abac6d2d216693f66fcc5a0bcbc1"); - expect(evm.signerAddress( + expect(signerAddress( evm.personalDigest("Signed, KimlikDAO"), evm.compactSignature( "0xf0912fda0d8d456eaa711fa58c3788813fc7a0ebe3906f38a5b57f18dd379b69" + "3a7afde0f05e858af8be9d8c62ca03dac9d52dc9c8cc2c00a4d74cb875988dae1c"))) .toBe("0x79883d9acbc4abac6d2d216693f66fcc5a0bcbc1") - expect(evm.signerAddress( + expect(signerAddress( evm.personalDigest("Çekoslovakyalılaştıramadıklarımızdanmışçasına"), evm.compactSignature( "0x8b00f90e6920617f49daad913d8492534d85a7dfc2a2ec0f8de149870fda62a5" + "0f7abf9b7488d8304deb635248e45ba65099d3bfc86ff43711311d4b4b18e1e41b"))) .toBe("0x79883d9acbc4abac6d2d216693f66fcc5a0bcbc1") - expect(evm.signerAddress( + expect(signerAddress( evm.personalDigest("Öşür yoğuşturup, aşı kovuşturmak. Iğdır’ın ilk harfi ı’dır."), evm.compactSignature( "0x2a64a7063ca328bee1d612a327757389b73970598f0e964b1ef075de3937c198" + @@ -53,11 +54,11 @@ describe("Signature tests", () => { test("signerAddress(d, signCompact(d, s)) == pointToAddress(s.G)", () => { /** @const {string} */ const digest = keccak256("sign me!"); - for (let /** bigint */ i = 1n; i < 1000n; ++i) - expect(evm.signerAddress(digest, evm.signCompact(digest, i))) - .toBe(evm.pointToAddress(G.copy().multiply(i).project())); + for (let /** bigint */ i = 1n; i < 100n; ++i) + expect(signerAddress(digest, signCompact(digest, i))) + .toBe(pointToAddress(G.copy().multiply(i).project())); }); test("pointToAddress()", () => { - expect(evm.pointToAddress(G)).toBe("0x7e5f4552091a69125d5dfcb7b8c2659029395bdf"); + expect(pointToAddress(G)).toBe("0x7e5f4552091a69125d5dfcb7b8c2659029395bdf"); }); diff --git a/mina/mina.js b/mina/mina.js index 9c2f24f..4010db9 100644 --- a/mina/mina.js +++ b/mina/mina.js @@ -1,16 +1,7 @@ -import { - G, - Point, - pointFrom, - signFields as signFieldsBigInt, - signMessage as signMessageBigInt, - verifyFields as verifyFieldsBigInt, - verifyMessage as verifyMessageBigInt -} from "../crypto/minaSchnorr"; -import { IC, f as sha256F } from "../crypto/sha2"; + +import { IC, g as sha256F } from "../crypto/sha2"; import base58 from "../util/base58"; import { uint8ArrayLEtoBigInt, uint8ArrayLEyeSayıdan } from "../util/çevir"; -import "./mina.d"; /** * @constructor @@ -35,12 +26,6 @@ PublicKey.fromBase58 = (addr) => { return new PublicKey(uint8ArrayLEtoBigInt(bytes.subarray(3, 35)), !!bytes[35]); } -/** - * @param {!Point} X - * @return {!PublicKey} - */ -PublicKey.fromPoint = (X) => new PublicKey(X.x, !!(X.y & 1n)); - /** @return {string} */ PublicKey.prototype.toBase58 = function () { /** @const {!Uint8Array} */ @@ -53,12 +38,6 @@ PublicKey.prototype.toBase58 = function () { return base58.from(buff); } -/** @return {!Point} */ -PublicKey.prototype.toPoint = function () { - // Public keys are always assumed to be valid point on the curve. - return /** @type {!Point} */(pointFrom(this.x, this.isOdd)); -} - /** * Modifies the input bytes * @@ -124,61 +103,6 @@ Signature.prototype.toBase58 = function () { return base58.from(buff); } -/** - * @param {!Array} fields - * @param {bigint} privKey - * @return {mina.SignerSignature} - */ -const signFields = (fields, privKey) => { - /** @const {!Point} */ - const X = G.copy().multiply(privKey).project(); - const { r, s } = signFieldsBigInt(fields, privKey, X); - return /** @type {mina.SignerSignature} */({ - signer: PublicKey.fromPoint(X).toBase58(), - signature: new Signature(r, s).toBase58() - }); -} - -/** - * @param {!Array} fields - * @param {mina.SignerSignature} sig - * @return {boolean} - */ -const verifyFields = (fields, sig) => { - const { r, s } = Signature.fromBase58(sig.signature); - /** @const {!Point} */ - const X = PublicKey.fromBase58(sig.signer).toPoint(); - return verifyFieldsBigInt(fields, r, s, X); -} - - -/** - * @param {string} message - * @param {bigint} privKey - * @return {mina.SignerSignature} - */ -const signMessage = (message, privKey) => { - /** @const {!Point} */ - const X = G.copy().multiply(privKey).project(); - const { r, s } = signMessageBigInt(message, privKey, X); - return /** @type {mina.SignerSignature} */({ - signer: PublicKey.fromPoint(X).toBase58(), - signature: new Signature(r, s).toBase58() - }); -} - -/** - * @param {string} message - * @param {mina.SignerSignature} sig - * @return {boolean} - */ -const verifyMessage = (message, sig) => { - const { r, s } = Signature.fromBase58(sig.signature); - /** @const {!Point} */ - const X = PublicKey.fromBase58(sig.signer).toPoint(); - return verifyMessageBigInt(message, r, s, X); -} - /** * @param {!Uint8Array} buff bytes array of which the last 4 bytes will be * written the checksum @@ -217,11 +141,7 @@ const addChecksum = (buff) => { } export { - PublicKey, - Signature, parsePrivateKey, - signFields, - signMessage, - verifyFields, - verifyMessage + PublicKey, + Signature }; diff --git a/mina/provider.d.js b/mina/provider.d.js index c18ecbf..fac7ef5 100644 --- a/mina/provider.d.js +++ b/mina/provider.d.js @@ -109,31 +109,31 @@ mina.Provider.prototype.getAccounts = function () { }; mina.Provider.prototype.requestAccounts = function () { }; /** - * @return {!Promise} + * @return {!Promise} */ mina.Provider.prototype.requestNetwork = function () { }; /** - * @param {!mina.SignMessageArgs} signMessageArgs - * @return {!Promise} + * @param {mina.SignMessageArgs} signMessageArgs + * @return {!Promise} */ mina.Provider.prototype.signMessage = function (signMessageArgs) { }; /** - * @param {!mina.SwitchChainArgs} switchChainArgs - * @return {!Promise} + * @param {mina.SwitchChainArgs} switchChainArgs + * @return {!Promise} */ mina.Provider.prototype.switchChain = function (switchChainArgs) { }; /** * @param {!mina.SignJsonMessageArgs} jsonMessage - * @return {!Promise} + * @return {!Promise} */ mina.Provider.prototype.signJsonMessage = function (jsonMessage) { }; /** - * @param {!mina.SendTransactionArgs} sendTransactionArgs - * @return {!Promise} + * @param {mina.SendTransactionArgs} sendTransactionArgs + * @return {!Promise} */ mina.Provider.prototype.sendTransaction = function (sendTransactionArgs) { }; diff --git a/mina/signer.js b/mina/signer.js new file mode 100644 index 0000000..5b2da48 --- /dev/null +++ b/mina/signer.js @@ -0,0 +1,84 @@ +import { + G, + Point, + pointFrom, + signFields as signFieldsBigInt, + signMessage as signMessageBigInt, + verifyFields as verifyFieldsBigInt, + verifyMessage as verifyMessageBigInt +} from "../crypto/minaSchnorr"; +import { PublicKey, Signature } from "./mina"; +import mina from "./mina.d"; + +/** + * @param {!Point} X + * @return {!PublicKey} + */ +PublicKey.fromPoint = (X) => new PublicKey(X.x, !!(X.y & 1n)); + +/** @return {!Point} */ +PublicKey.prototype.toPoint = function () { + // Public keys are always assumed to be valid point on the curve. + return /** @type {!Point} */(pointFrom(this.x, this.isOdd)); +} + +/** + * @param {!Array} fields + * @param {bigint} privKey + * @return {mina.SignerSignature} + */ +const signFields = (fields, privKey) => { + /** @const {!Point} */ + const X = G.copy().multiply(privKey).project(); + const { r, s } = signFieldsBigInt(fields, privKey, X); + return /** @type {mina.SignerSignature} */({ + signer: PublicKey.fromPoint(X).toBase58(), + signature: new Signature(r, s).toBase58() + }); +} + +/** + * @param {!Array} fields + * @param {mina.SignerSignature} sig + * @return {boolean} + */ +const verifyFields = (fields, sig) => { + const { r, s } = Signature.fromBase58(sig.signature); + /** @const {!Point} */ + const X = PublicKey.fromBase58(sig.signer).toPoint(); + return verifyFieldsBigInt(fields, r, s, X); +} + +/** + * @param {string} message + * @param {bigint} privKey + * @return {mina.SignerSignature} + */ +const signMessage = (message, privKey) => { + /** @const {!Point} */ + const X = G.copy().multiply(privKey).project(); + const { r, s } = signMessageBigInt(message, privKey, X); + return /** @type {mina.SignerSignature} */({ + signer: PublicKey.fromPoint(X).toBase58(), + signature: new Signature(r, s).toBase58() + }); +} + +/** + * @param {string} message + * @param {mina.SignerSignature} sig + * @return {boolean} + */ +const verifyMessage = (message, sig) => { + const { r, s } = Signature.fromBase58(sig.signature); + /** @const {!Point} */ + const X = PublicKey.fromBase58(sig.signer).toPoint(); + return verifyMessageBigInt(message, r, s, X); +} + +export { + signFields, + signMessage, + verifyFields, + verifyMessage +}; diff --git a/mina/test/mina.test.js b/mina/test/mina.test.js index 1c3cce5..18c0a15 100644 --- a/mina/test/mina.test.js +++ b/mina/test/mina.test.js @@ -1,6 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { G, Point } from "../../crypto/minaSchnorr"; -import { parsePrivateKey, PublicKey, signFields, verifyFields } from "../mina"; +import { PublicKey } from "../mina"; describe("PublicKey", () => { test("to/from base58", () => { @@ -17,19 +16,9 @@ describe("PublicKey", () => { expect(pk.toBase58()).toBe(pk58); }); - test("to/from Point", () => { - /** @const {!Point} */ - const X = G.copy().multiply(0x13371337n).project(); - /** @const {!PublicKey} */ - const pk = PublicKey.fromPoint(X); - expect(pk.toPoint()).toEqual(X); - }); - test("serialize into / deserialize from bytes", () => { - /** @const {!Point} */ - const X = G.copy().multiply(0x13371337n).project(); /** @const {!PublicKey} */ - const pk = PublicKey.fromPoint(X); + const pk = new PublicKey(123123n, true); /** @const {!Uint8Array} */ const buff = new Uint8Array(32); @@ -40,16 +29,3 @@ describe("PublicKey", () => { expect(pk2).toEqual(pk); }) }); - -describe("sign/verify fields", () => { - test("smoke tests", () => { - /** @const {bigint} */ - const s = parsePrivateKey("EKF5WGqhkg3yQyiRU2gWC1W1KLw2xLuRgwtQNEbZ5qWqGYpktw8S"); - /** @const {!Point} */ - const X = G.copy().multiply(s).project(); - /** @const {mina.SignerSignature} */ - const sig = signFields([1n, 31n, 1337n], s); - expect(sig.signer).toBe(PublicKey.fromPoint(X).toBase58()); - expect(verifyFields([1n, 31n, 1337n], sig)).toBeTrue(); - }); -}); diff --git a/mina/test/signer.test.js b/mina/test/signer.test.js new file mode 100644 index 0000000..248bb59 --- /dev/null +++ b/mina/test/signer.test.js @@ -0,0 +1,26 @@ +import { describe, expect, test } from "bun:test"; +import { G, Point } from "../../crypto/minaSchnorr"; +import { parsePrivateKey, PublicKey } from "../mina"; +import mina from "../mina.d"; +import { signFields, verifyFields } from "../signer"; + +test("to/from Point", () => { + /** @const {!Point} */ + const X = G.copy().multiply(0x13371337n).project(); + /** @const {!PublicKey} */ + const pk = PublicKey.fromPoint(X); + expect(pk.toPoint()).toEqual(X); +}); + +describe("sign/verify fields", () => { + test("smoke tests", () => { + /** @const {bigint} */ + const s = parsePrivateKey("EKF5WGqhkg3yQyiRU2gWC1W1KLw2xLuRgwtQNEbZ5qWqGYpktw8S"); + /** @const {!Point} */ + const X = G.copy().multiply(s).project(); + /** @const {mina.SignerSignature} */ + const sig = signFields([1n, 31n, 1337n], s); + expect(sig.signer).toBe(PublicKey.fromPoint(X).toBase58()); + expect(verifyFields([1n, 31n, 1337n], sig)).toBeTrue(); + }); +});