@@ -2,6 +2,8 @@ import { describe, expect, it } from "vitest";
22import { ccc } from "../../index.js" ;
33
44const client = new ccc . ClientPublicTestnet ( ) ;
5+ const ZERO_HASH =
6+ "0x0000000000000000000000000000000000000000000000000000000000000000" ;
57
68describe ( "MultisigCkbWitness" , ( ) => {
79 it ( "should encode and decode correctly" , ( ) => {
@@ -57,6 +59,52 @@ describe("MultisigCkbWitness", () => {
5759 "0x6418f118e94d8dff7d9b0b59a4d837c4e201c5a9" ,
5860 ) ;
5961 } ) ;
62+
63+ describe ( "signature matching logic" , ( ) => {
64+ const privKey1 =
65+ "0x0000000000000000000000000000000000000000000000000000000000000001" ;
66+ const privKey2 =
67+ "0x0000000000000000000000000000000000000000000000000000000000000002" ;
68+ const privKey3 =
69+ "0x0000000000000000000000000000000000000000000000000000000000000003" ;
70+ const signer1 = new ccc . SignerCkbPrivateKey ( client , privKey1 ) ;
71+ const signer2 = new ccc . SignerCkbPrivateKey ( client , privKey2 ) ;
72+ const signer3 = new ccc . SignerCkbPrivateKey ( client , privKey3 ) ;
73+
74+ const message = ccc . hashCkb ( "0x0123456789abcdef" ) ;
75+
76+ it ( "should count signatures when required signers signed" , async ( ) => {
77+ const sig1 = await signer1 . _signMessage ( message ) ;
78+ const sig2 = await signer2 . _signMessage ( message ) ;
79+
80+ const witness = ccc . MultisigCkbWitness . from ( {
81+ publicKeys : [ signer1 . publicKey , signer2 . publicKey , signer3 . publicKey ] ,
82+ threshold : 2 ,
83+ mustMatch : 1 , // signer1 is required
84+ signatures : [ sig1 , sig2 ] ,
85+ } ) ;
86+
87+ const counts = witness . calcMatchedSignaturesCount ( message ) ;
88+ expect ( counts . required ) . toBe ( 1 ) ;
89+ expect ( counts . flexible ) . toBe ( 1 ) ;
90+ } ) ;
91+
92+ it ( "should count signatures when only flexible signers signed" , async ( ) => {
93+ const sig2 = await signer2 . _signMessage ( message ) ;
94+ const sig3 = await signer3 . _signMessage ( message ) ;
95+
96+ const witness = ccc . MultisigCkbWitness . from ( {
97+ publicKeys : [ signer1 . publicKey , signer2 . publicKey , signer3 . publicKey ] ,
98+ threshold : 2 ,
99+ mustMatch : 1 ,
100+ signatures : [ sig2 , sig3 ] ,
101+ } ) ;
102+
103+ const counts = witness . calcMatchedSignaturesCount ( message ) ;
104+ expect ( counts . required ) . toBe ( 0 ) ;
105+ expect ( counts . flexible ) . toBe ( 2 ) ;
106+ } ) ;
107+ } ) ;
60108} ) ;
61109
62110describe ( "SignerMultisigCkbReadonly" , ( ) => {
@@ -74,5 +122,199 @@ describe("SignerMultisigCkbReadonly", () => {
74122
75123 expect ( await signer . getMemberCount ( ) ) . toBe ( 2 ) ;
76124 expect ( await signer . getMemberThreshold ( ) ) . toBe ( 1 ) ;
125+ expect ( await signer . getMemberRequiredCount ( ) ) . toBe ( 0 ) ;
126+ } ) ;
127+
128+ describe ( "getSignaturesCount with mustMatch" , ( ) => {
129+ const privKey1 =
130+ "0x0000000000000000000000000000000000000000000000000000000000000001" ;
131+ const privKey2 =
132+ "0x0000000000000000000000000000000000000000000000000000000000000002" ;
133+ const privKey3 =
134+ "0x0000000000000000000000000000000000000000000000000000000000000003" ;
135+ const signer1 = new ccc . SignerCkbPrivateKey ( client , privKey1 ) ;
136+ const signer2 = new ccc . SignerCkbPrivateKey ( client , privKey2 ) ;
137+ const signer3 = new ccc . SignerCkbPrivateKey ( client , privKey3 ) ;
138+
139+ const multisigSigner = new ccc . SignerMultisigCkbReadonly ( client , {
140+ publicKeys : [ signer1 . publicKey , signer2 . publicKey , signer3 . publicKey ] ,
141+ threshold : 2 ,
142+ mustMatch : 1 , // signer1 is required
143+ } ) ;
144+
145+ const message = ccc . hashCkb ( "0x0123456789abcdef" ) ;
146+ multisigSigner . getSignInfo = async ( ) => ( {
147+ message : message ,
148+ position : 0 ,
149+ } ) ;
150+
151+ const getTx = ( signatures : string [ ] ) =>
152+ ccc . Transaction . from ( {
153+ inputs : [ { previousOutput : { txHash : ZERO_HASH , index : 0 } , since : 0 } ] ,
154+ witnesses : [
155+ ccc . WitnessArgs . from ( {
156+ lock : ccc . MultisigCkbWitness . from ( {
157+ publicKeyHashes : multisigSigner . multisigInfo . publicKeyHashes ,
158+ threshold : 2 ,
159+ mustMatch : 1 ,
160+ signatures,
161+ } ) . toBytes ( ) ,
162+ } ) . toBytes ( ) ,
163+ ] ,
164+ } ) ;
165+
166+ it ( "should return 1 when only required signer signed" , async ( ) => {
167+ const sig1 = await signer1 . _signMessage ( message ) ;
168+ const tx = getTx ( [ sig1 ] ) ;
169+ expect ( await multisigSigner . getSignaturesCount ( tx ) ) . toBe ( 1 ) ;
170+ expect ( await multisigSigner . needMoreSignatures ( tx ) ) . toBe ( true ) ;
171+ } ) ;
172+
173+ it ( "should return 1 when only flexible signers signed" , async ( ) => {
174+ const sig2 = await signer2 . _signMessage ( message ) ;
175+ const sig3 = await signer3 . _signMessage ( message ) ;
176+ const tx = getTx ( [ sig2 , sig3 ] ) ;
177+ expect ( await multisigSigner . getSignaturesCount ( tx ) ) . toBe ( 1 ) ;
178+ expect ( await multisigSigner . needMoreSignatures ( tx ) ) . toBe ( true ) ;
179+ } ) ;
180+
181+ it ( "should return 2 when required and flexible signers signed" , async ( ) => {
182+ const sig1 = await signer1 . _signMessage ( message ) ;
183+ const sig2 = await signer2 . _signMessage ( message ) ;
184+ const tx = getTx ( [ sig1 , sig2 ] ) ;
185+ expect ( await multisigSigner . getSignaturesCount ( tx ) ) . toBe ( 2 ) ;
186+ expect ( await multisigSigner . needMoreSignatures ( tx ) ) . toBe ( false ) ;
187+ } ) ;
188+ } ) ;
189+
190+ describe ( "aggregateTransactions with mustMatch" , ( ) => {
191+ const privKey1 =
192+ "0x0000000000000000000000000000000000000000000000000000000000000001" ;
193+ const privKey2 =
194+ "0x0000000000000000000000000000000000000000000000000000000000000002" ;
195+ const privKey3 =
196+ "0x0000000000000000000000000000000000000000000000000000000000000003" ;
197+ const signer1 = new ccc . SignerCkbPrivateKey ( client , privKey1 ) ;
198+ const signer2 = new ccc . SignerCkbPrivateKey ( client , privKey2 ) ;
199+ const signer3 = new ccc . SignerCkbPrivateKey ( client , privKey3 ) ;
200+
201+ const multisigSigner = new ccc . SignerMultisigCkbReadonly ( client , {
202+ publicKeys : [ signer1 . publicKey , signer2 . publicKey , signer3 . publicKey ] ,
203+ threshold : 2 ,
204+ mustMatch : 1 , // signer1 is required
205+ } ) ;
206+
207+ const message = ccc . hashCkb ( "0x0123456789abcdef" ) ;
208+ multisigSigner . getSignInfo = async ( ) => ( {
209+ message : message ,
210+ position : 0 ,
211+ } ) ;
212+
213+ it ( "should aggregate required signatures from different transactions" , async ( ) => {
214+ const sig1 = await signer1 . _signMessage ( message ) ;
215+ const sig2 = await signer2 . _signMessage ( message ) ;
216+ const sig3 = await signer3 . _signMessage ( message ) ;
217+
218+ const tx1 = ccc . Transaction . from ( {
219+ inputs : [ { previousOutput : { txHash : ZERO_HASH , index : 0 } , since : 0 } ] ,
220+ witnesses : [
221+ ccc . WitnessArgs . from ( {
222+ lock : ccc . MultisigCkbWitness . from ( {
223+ publicKeyHashes : multisigSigner . multisigInfo . publicKeyHashes ,
224+ threshold : 2 ,
225+ mustMatch : 1 ,
226+ signatures : [ sig2 , sig3 ] , // Missing required
227+ } ) . toBytes ( ) ,
228+ } ) . toBytes ( ) ,
229+ ] ,
230+ } ) ;
231+
232+ const tx2 = ccc . Transaction . from ( {
233+ inputs : [ { previousOutput : { txHash : ZERO_HASH , index : 0 } , since : 0 } ] ,
234+ witnesses : [
235+ ccc . WitnessArgs . from ( {
236+ lock : ccc . MultisigCkbWitness . from ( {
237+ publicKeyHashes : multisigSigner . multisigInfo . publicKeyHashes ,
238+ threshold : 2 ,
239+ mustMatch : 1 ,
240+ signatures : [ sig1 ] , // Contains required
241+ } ) . toBytes ( ) ,
242+ } ) . toBytes ( ) ,
243+ ] ,
244+ } ) ;
245+
246+ const aggregatedTx = await multisigSigner . aggregateTransactions ( [
247+ tx1 ,
248+ tx2 ,
249+ ] ) ;
250+ const decodedWitness = multisigSigner . decodeWitnessArgsAt (
251+ aggregatedTx ,
252+ 0 ,
253+ ) ! ;
254+
255+ const { required, flexible } =
256+ decodedWitness . calcMatchedSignaturesCount ( message ) ;
257+ expect ( required ) . toBe ( 1 ) ;
258+ expect ( flexible ) . toBe ( 1 ) ;
259+ expect ( decodedWitness . signatures . length ) . toBe ( 2 ) ;
260+ } ) ;
261+ } ) ;
262+ } ) ;
263+
264+ describe ( "SignerMultisigCkbPrivateKey" , ( ) => {
265+ const privKey1 =
266+ "0x0000000000000000000000000000000000000000000000000000000000000001" ;
267+ const privKey2 =
268+ "0x0000000000000000000000000000000000000000000000000000000000000002" ;
269+ const privKey3 =
270+ "0x0000000000000000000000000000000000000000000000000000000000000003" ;
271+ const signer1 = new ccc . SignerCkbPrivateKey ( client , privKey1 ) ;
272+ const signer2 = new ccc . SignerCkbPrivateKey ( client , privKey2 ) ;
273+ const signer3 = new ccc . SignerCkbPrivateKey ( client , privKey3 ) ;
274+
275+ const multisigWitness : ccc . MultisigCkbWitnessLike = {
276+ publicKeys : [ signer1 . publicKey , signer2 . publicKey , signer3 . publicKey ] ,
277+ threshold : 2 ,
278+ mustMatch : 1 , // signer1 is required
279+ } ;
280+
281+ const message = ccc . hashCkb ( "0x0123456789abcdef" ) ;
282+
283+ it ( "should replace a flexible signature with a required one if threshold reached" , async ( ) => {
284+ const sig2 = await signer2 . _signMessage ( message ) ;
285+ const sig3 = await signer3 . _signMessage ( message ) ;
286+
287+ const multisigSigner1 = new ccc . SignerMultisigCkbPrivateKey (
288+ client ,
289+ privKey1 ,
290+ multisigWitness ,
291+ ) ;
292+ multisigSigner1 . getSignInfo = async ( ) => ( {
293+ message : message ,
294+ position : 0 ,
295+ } ) ;
296+
297+ const tx = ccc . Transaction . from ( {
298+ inputs : [ { previousOutput : { txHash : ZERO_HASH , index : 0 } , since : 0 } ] ,
299+ witnesses : [
300+ ccc . WitnessArgs . from ( {
301+ lock : ccc . MultisigCkbWitness . from ( {
302+ publicKeyHashes : multisigSigner1 . multisigInfo . publicKeyHashes ,
303+ threshold : 2 ,
304+ mustMatch : 1 ,
305+ signatures : [ sig2 , sig3 ] ,
306+ } ) . toBytes ( ) ,
307+ } ) . toBytes ( ) ,
308+ ] ,
309+ } ) ;
310+
311+ const signedTx = await multisigSigner1 . signOnlyTransaction ( tx ) ;
312+ const decodedWitness = multisigSigner1 . decodeWitnessArgsAt ( signedTx , 0 ) ! ;
313+
314+ const { required, flexible } =
315+ decodedWitness . calcMatchedSignaturesCount ( message ) ;
316+ expect ( required ) . toBe ( 1 ) ;
317+ expect ( flexible ) . toBe ( 1 ) ;
318+ expect ( decodedWitness . signatures . length ) . toBe ( 2 ) ;
77319 } ) ;
78320} ) ;
0 commit comments