From 5ec5fc82cc04228fc33a28cc6669ed76e9152f5a Mon Sep 17 00:00:00 2001 From: Alejo Acosta Date: Thu, 5 Dec 2024 18:26:38 -0300 Subject: [PATCH] fix qiwallet signing unit test --- examples/signing/sign-verify-qi-schnorr.js | 30 ++++++----- src/_tests/types.ts | 6 --- ...t.test.ts => qihdwallet-sign.unit.test.ts} | 50 +++++++++++++----- src/wallet/qi-hdwallet.ts | 10 +++- testcases/qi-sign-message.json.gz | Bin 203 -> 648 bytes testcases/qi-transaction.json.gz | Bin 910 -> 986 bytes 6 files changed, 62 insertions(+), 34 deletions(-) rename src/_tests/unit/{qihdwallet.unit.test.ts => qihdwallet-sign.unit.test.ts} (66%) diff --git a/examples/signing/sign-verify-qi-schnorr.js b/examples/signing/sign-verify-qi-schnorr.js index cee61986..096e329e 100644 --- a/examples/signing/sign-verify-qi-schnorr.js +++ b/examples/signing/sign-verify-qi-schnorr.js @@ -1,15 +1,22 @@ -const quais = require('../../lib/commonjs/quais'); +const { + Mnemonic, + QiHDWallet, + Zone, + QiTransaction, + getBytes, + keccak256, +} = require('../../lib/commonjs/quais'); require('dotenv').config(); -const { keccak_256 } = require('@noble/hashes/sha3'); + const { schnorr } = require('@noble/curves/secp256k1'); async function main() { // Create wallet - const mnemonic = quais.Mnemonic.fromPhrase(process.env.MNEMONIC); - const qiWallet = quais.QiHDWallet.fromMnemonic(mnemonic); + const mnemonic = Mnemonic.fromPhrase(process.env.MNEMONIC); + const qiWallet = QiHDWallet.fromMnemonic(mnemonic); // Get address info - const addressInfo1 = await qiWallet.getNextAddress(0, quais.Zone.Cyprus1); + const addressInfo1 = await qiWallet.getNextAddress(0, Zone.Cyprus1); const addr1 = addressInfo1.address; const pubkey1 = addressInfo1.pubKey; @@ -23,13 +30,12 @@ async function main() { denomination: 7, }, address: addr1, - zone: quais.Zone.Cyprus1, + zone: Zone.Cyprus1, }, ]; // Polulate wallet with outpoints qiWallet.importOutpoints(outpointsInfo); - // Define tx inputs, outputs for the Qi Tx let txInputs = [ { @@ -47,18 +53,18 @@ async function main() { ]; // Create the Qi Tx to be signed - const tx = new quais.QiTransaction(); + const tx = new QiTransaction(); tx.txInputs = txInputs; tx.txOutputs = txOutputs; // Calculate the hash of the Qi tx (message to be signed and verified) - const txHash = keccak_256(tx.unsignedSerialized); + const txHash = getBytes(keccak256(tx.unsignedSerialized)); // Sign the tx const serializedSignedTx = await qiWallet.signTransaction(tx); // Unmarshall the signed Tx - const signedTx = quais.QiTransaction.from(serializedSignedTx); + const signedTx = QiTransaction.from(serializedSignedTx); // Get the signature from the signed tx const signature = signedTx.signature; @@ -66,8 +72,8 @@ async function main() { // Remove parity byte from pubkey publicKey = '0x' + pubkey1.slice(4); - // Rerify the schnoor signature - const verified = schnorr.verify(quais.getBytes(signature), txHash, quais.getBytes(publicKey)); + // Verify the schnoor signature + const verified = schnorr.verify(getBytes(signature), txHash, getBytes(publicKey)); console.log('Verified:', verified); } diff --git a/src/_tests/types.ts b/src/_tests/types.ts index 63217f6f..5cdd2fba 100644 --- a/src/_tests/types.ts +++ b/src/_tests/types.ts @@ -344,9 +344,3 @@ export interface TestCaseQiTransaction { }; signed: string; } - -export interface TestCaseQiSignMessage { - name: string; - mnemonic: string; - message: string; -} diff --git a/src/_tests/unit/qihdwallet.unit.test.ts b/src/_tests/unit/qihdwallet-sign.unit.test.ts similarity index 66% rename from src/_tests/unit/qihdwallet.unit.test.ts rename to src/_tests/unit/qihdwallet-sign.unit.test.ts index 9a7dbf3e..d095354a 100644 --- a/src/_tests/unit/qihdwallet.unit.test.ts +++ b/src/_tests/unit/qihdwallet-sign.unit.test.ts @@ -2,11 +2,19 @@ import assert from 'assert'; import { loadTests } from '../utils.js'; import { schnorr } from '@noble/curves/secp256k1'; -import { keccak_256 } from '@noble/hashes/sha3'; import { MuSigFactory } from '@brandonblack/musig'; -import { TestCaseQiSignMessage, TestCaseQiTransaction, TxInput, TxOutput, Zone } from '../types.js'; +import { TestCaseQiTransaction, TxInput, TxOutput, Zone } from '../types.js'; -import { Mnemonic, QiHDWallet, QiTransaction, getBytes, hexlify, musigCrypto } from '../../index.js'; +import { + Mnemonic, + QiHDWallet, + QiTransaction, + getBytes, + hexlify, + musigCrypto, + keccak256, + toUtf8Bytes, +} from '../../index.js'; describe('QiHDWallet: Test transaction signing', function () { const tests = loadTests('qi-transaction'); @@ -23,7 +31,7 @@ describe('QiHDWallet: Test transaction signing', function () { test.transaction.txInputs, test.transaction.txOutputs, ); - const digest = keccak_256(qiTx.unsignedSerialized); + const digest = getBytes(keccak256(qiTx.unsignedSerialized)); const signedSerialized = await qiWallet.signTransaction(qiTx); const signedTx = QiTransaction.from(signedSerialized); @@ -41,18 +49,32 @@ describe('QiHDWallet: Test transaction signing', function () { } }); +interface signMessageTestCase { + mnemonic: string; + data: Array<{ + name: string; + message: string; + }>; +} + describe('QiHDWallet: Test sign personal menssage', function () { - const tests = loadTests('qi-sign-message'); + const tests = loadTests('qi-sign-message'); for (const test of tests) { - it(`tests signing personal message: ${test.name}`, async function () { - const mnemonic = Mnemonic.fromPhrase(test.mnemonic); - const qiWallet = QiHDWallet.fromMnemonic(mnemonic); - const addrInfo = qiWallet.getNextAddressSync(0, Zone.Cyprus1); - const signature = await qiWallet.signMessage(addrInfo.address, test.message); - const digest = keccak_256(test.message); - const verified = verifySchnorrSignature(signature, digest, addrInfo.pubKey); - assert.equal(verified, true); - }); + const mnemonic = Mnemonic.fromPhrase(test.mnemonic); + const qiWallet = QiHDWallet.fromMnemonic(mnemonic); + const addrInfo = qiWallet.getNextAddressSync(0, Zone.Cyprus1); + for (const data of test.data) { + it(`tests signing personal message: ${data.name}`, async function () { + const signature = await qiWallet.signMessage(addrInfo.address, data.message); + const messageBytes = + typeof data.message === 'string' + ? getBytes(toUtf8Bytes(data.message)) // Add UTF-8 encoding for strings + : data.message; + const digest = getBytes(keccak256(messageBytes)); + const verified = verifySchnorrSignature(signature, digest, addrInfo.pubKey); + assert.equal(verified, true); + }); + } } }); diff --git a/src/wallet/qi-hdwallet.ts b/src/wallet/qi-hdwallet.ts index 61c5d1d8..ccb30aa7 100644 --- a/src/wallet/qi-hdwallet.ts +++ b/src/wallet/qi-hdwallet.ts @@ -32,6 +32,7 @@ import ecc from '@bitcoinerlab/secp256k1'; import { SelectedCoinsResult } from '../transaction/abstract-coinselector.js'; import { QiPerformActionTransaction } from '../providers/abstract-provider.js'; import { ConversionCoinSelector } from '../transaction/coinselector-conversion.js'; +import { toUtf8Bytes } from '../quais.js'; /** * @property {Outpoint} outpoint - The outpoint object. @@ -1617,8 +1618,13 @@ export class QiHDWallet extends AbstractHDWallet { */ public async signMessage(address: string, message: string | Uint8Array): Promise { const privKey = this.getPrivateKey(address); - const digest = keccak256(message); - const signature = schnorr.sign(digest, getBytes(privKey)); + const messageBytes = + typeof message === 'string' + ? getBytes(toUtf8Bytes(message)) // Add UTF-8 encoding to support arbitrary strings + : message; + const digest = keccak256(messageBytes); + const digestBytes = getBytes(digest); + const signature = schnorr.sign(digestBytes, getBytes(privKey)); return hexlify(signature); } diff --git a/testcases/qi-sign-message.json.gz b/testcases/qi-sign-message.json.gz index c1464076813538d85042b62448a5d58896008df7..c767fbfce5ec5fd1f8c51ac532b49ee558fac56f 100644 GIT binary patch literal 648 zcmV;30(bo%iwFpA7*b~d1953Bb7^O8Ep26Ub75y?E^2dcZUD8Ey>1jS5P<9CDJI8{ z09_)IP@q%@6hr|uNrXfaB=7EUH(+}Uuw%E_nFen3rO z0oKH%i~*_ehNDy>xf6v`$|x&rfFgiaiQoal7%c*-j#Z#E?G$POHUgj}BWy7<4tnya zOdECz@?cqBcaZ@O(4`bqh}WgiTA7r`QmlH^%;r?C}YczcQVl;fe zI~u&~D{Ipl?6_X9Sb@s}5$+1C%c>O^%RSxV`{;PSPnmp5rd|~JLP^6D ijib>^5Cd!J6^!b< zATRI_QW!2y(#Be3_bA-0B@2Q7ZeNft6$ua|hUdQf>AP;o$z}M!a1t}(lf~w@;k_lE z#hG<#srbrW3MTR!o6BW;Or- diff --git a/testcases/qi-transaction.json.gz b/testcases/qi-transaction.json.gz index 59a6e60b7f7a819ceb112fa1a68b4128261e5516..8bce2352590a27df9eb5206fed5b5dff19efb5f4 100644 GIT binary patch literal 986 zcmV<0110<)iwFpo22y7L1953BbaG*Cb75n2X>V>WYIARH0PR-EjvF@+z56Q&-2w;* zcd~Mu#RK@14={Wv7K>KH>TW@5&62?Xo@z;BO`HWD8$kxJ4@6U}?x$C;ijREzN~zyv zD76@{=fy8-VN^G4_LKTq88z-FnJ$*cH~NA5ap-7%i~G%Z$6H0?_(9!vui( zIH ze$$cSzKr!jt$4j2Rbg&wx9=v^?Z)*C)_3<@)x`Ic+HJ;9TsOGIekV-d9)3S&d59s= zxF047>g9R$w{bY+ukW>9oR&Wxp6^bWvl?SJO#cA;7?$XHRo18b74B9?Xk(oZF=fzS zH(M;9B6maO`#+~4tCfeb??h>-g#^>5*vBW?Vy#==?H&_`W`Yl39fk{|gS|i#?M)7u zZ2=o{bXV0~FPq2W%2&cGfoMvR|C_KF!`O&$<74*irwI{AY#9F5cM7E0be$ zhW)iQUKbM`r7I!lUguNAGn@~WG%g+t#$<2 zYU8U$Uo_e%!^-%|@||;(PXhXVsgVDMgGZ~XhuEbOw zIG1!Y6*R|;+8ESmvan8OR&18B*+P#p*0YQ>%g9F=Va`l50ud0`+(=LgBBt6BOTjtn zFawoF;9aC#Ei?d44NM7~2n0gFThpPT14_-3%~>+5CNh*7L(5gZ@t#{P;7ybL0VRgSi@DbCOM~n9Cyz5b}Mjb(H+ee#x2IGmv;Aayj zqX~3Kt{~JX1`qQ7cX6D99GNfoK>yE}IZw0R78xnWAQuE3iO@Ldh>9f`LI?$_p97v8 z1SyWI)+X23$R_#LtHyk($YgExV#S8FWs@mz2q2qIQ*IJCQjIksitcO-AGC7+vy4FGpCgl`sOq!A>40Feugq&gVq&INdZ%-EYQfSrg zJ@2L?Cum|ntwDz90747|6)aIht~H>XZN?QBr4oXKo>5HAg}_xtk?ua+s$RYO1FXMl I74Z%L0Iwk7qyPW_ literal 910 zcmV;919AKxiwFqj7KCO11953BbaG*Cb75n2X>V>WYIARH0PR-Ej@vj8z2_?k-3Bl) z+?U*9PdAX$d_d5LVzC$tkwn9iyWKO$zYpalb}|dnX#~?iav(^t#HV_%N>zOKim^ZI zV5}LSr{*4Ca?s#y1Bp6fAUsSGd?gWa?)Th>G0L&rwwCblh)^K#wZSIpRMr!_0xW4~#U zQ6EcsWNYfW!Xi;-)SmgHqm}!ELiLU0w7coU6@ z!UWemNv>%N!w7hC<19&)OaVT5g4!Rtz52zw^<3&ze%Ba z6jq^I8Sv`HJE2`}zA^#+<`Ax-9p0w-Yr%%2>!&nn#`gU{>Ewv#QURA-Vepa*ojB;o zLE1WG9p4z9%Sv!wXDOZ^01sRU;2?bU+KE}QS;j0Qo@GoI8F7}8jxvUs87Byc0V(;6 z6(s_IOWBc0kt{beP|O&#vgnfxIRuV5h-k=Rs1OxAXKpg6fG$gv-dAQxSPC&&lYOdZ zw5FV5&?3hmHB|J>V^%y6&QUnT;ETd6kA-=>s?5Lqz@>TdtkXXW`;!X$KZyN8RWVAOt5@+WXs|?e1yqZt(*7DUEY!mq38tn%V=nMFgP; zAVD+&A($AFo(&pRFtrb;B&AcCgp2BjXy>7}%$M8Hznf>zPLAhZHRel&M=4XSL0%A- zBs>~2CMePzeaw=0j5d+20F{11D>0E%Rz?F#lMv)nilTa1E4$N1>X2gsLM5FCm9ue1 zIPYw-j-%5EMk9f6Kt42AxdGE|dn$1+#jVu7r*S&^6tbs~bMV2c5R5e#QjmxlC?x}e k8=<1IH4+sGG=Y;f+YF^TV$JTut?Jd?U&|=*CGidb06g2fkN^Mx