@@ -696,6 +696,247 @@ pub const Mint = struct {
696
696
.signatures = try blind_signatures .toOwnedSlice (),
697
697
};
698
698
}
699
+
700
+ /// Fee required for proof set
701
+ pub fn getProofsFee (self : * Mint , gpa : std.mem.Allocator , proofs : []const nuts.Proof ) ! core.amount.Amount {
702
+ var sum_fee : u64 = 0 ;
703
+
704
+ for (proofs ) | proof | {
705
+ const input_fee_ppk = try self
706
+ .localstore
707
+ .value
708
+ .getKeysetInfo (gpa , proof .keyset_id ) orelse return error .UnknownKeySet ;
709
+ defer input_fee_ppk .deinit (gpa );
710
+
711
+ sum_fee += input_fee_ppk .input_fee_ppk ;
712
+ }
713
+
714
+ const fee = (sum_fee + 999 ) / 1000 ;
715
+
716
+ return fee ;
717
+ }
718
+
719
+ /// Check Tokens are not spent or pending
720
+ pub fn checkYsSpendable (
721
+ self : * Mint ,
722
+ ys : []const secp256k1.PublicKey ,
723
+ proof_state : nuts.nut07.State ,
724
+ ) ! void {
725
+ const proofs_state = try self
726
+ .localstore
727
+ .value
728
+ .updateProofsStates (self .allocator , ys , proof_state );
729
+ defer proofs_state .deinit ();
730
+
731
+ for (proofs_state .items ) | p |
732
+ if (p ) | proof | switch (proof ) {
733
+ .pending = > return error .TokenPending ,
734
+ .spent = > return error .TokenAlreadySpent ,
735
+ else = > continue ,
736
+ };
737
+ }
738
+
739
+ /// Verify [`Proof`] meets conditions and is signed
740
+ pub fn verifyProof (self : * Mint , proof : nuts.Proof ) ! void {
741
+ // Check if secret is a nut10 secret with conditions
742
+ if (nuts .nut10 .Secret .fromSecret (proof .secret , self .allocator )) | secret | {
743
+ defer secret .deinit ();
744
+
745
+ // Checks and verifes known secret kinds.
746
+ // If it is an unknown secret kind it will be treated as a normal secret.
747
+ // Spending conditions will **not** be check. It is up to the wallet to ensure
748
+ // only supported secret kinds are used as there is no way for the mint to enforce
749
+ // only signing supported secrets as they are blinded at that point.
750
+
751
+ switch (secret .value .kind ) {
752
+ .p2pk = > try nuts .nut11 .verifyP2pkProof (& proof , self .allocator ),
753
+ .htlc = > try nuts .verifyHTLC (& proof , self .allocator ),
754
+ }
755
+ } else | _ | {}
756
+
757
+ try self .ensureKeysetLoaded (proof .keyset_id );
758
+
759
+ const sec_key = v : {
760
+ self .keysets .lock .lock ();
761
+ defer self .keysets .lock .unlock ();
762
+ const keyset = self .keysets .value .get (proof .keyset_id ) orelse return error .UnknownKeySet ;
763
+
764
+ break :v (keyset .keys .inner .get (proof .amount ) orelse return error .AmountKey ).secret_key ;
765
+ };
766
+
767
+ try core .dhke .verifyMessage (self .secp_ctx , sec_key , proof .c , proof .secret .toBytes ());
768
+ }
769
+
770
+ /// Process Swap
771
+ /// expecting allocator as arena
772
+ pub fn processSwapRequest (
773
+ self : * Mint ,
774
+ arena : std.mem.Allocator ,
775
+ swap_request : nuts.SwapRequest ,
776
+ ) ! nuts.SwapResponse {
777
+ var blinded_messages = try std .ArrayList (secp256k1 .PublicKey ).initCapacity (arena , swap_request .outputs .len );
778
+
779
+ for (swap_request .outputs ) | b | {
780
+ blinded_messages .appendAssumeCapacity (b .blinded_secret );
781
+ }
782
+
783
+ const _blind_signatures = try self .localstore .value .getBlindSignatures (arena , blinded_messages .items );
784
+
785
+ for (_blind_signatures .items ) | bs | {
786
+ if (bs != null ) {
787
+ std .log .debug ("output has already been signed" , .{});
788
+
789
+ return error .BlindedMessageAlreadySigned ;
790
+ }
791
+ }
792
+
793
+ const proofs_total = swap_request .inputAmount ();
794
+ const output_total = swap_request .outputAmount ();
795
+
796
+ const fee = try self .getProofsFee (arena , swap_request .inputs );
797
+
798
+ if (proofs_total < output_total + fee ) {
799
+ std .log .info ("Swap request without enough inputs: {}, outputs {}, fee {}" , .{
800
+ proofs_total , output_total , fee ,
801
+ });
802
+
803
+ return error .InsufficientInputs ;
804
+ }
805
+
806
+ var input_ys = try std .ArrayList (secp256k1 .PublicKey ).initCapacity (arena , swap_request .inputs .len );
807
+
808
+ for (swap_request .inputs ) | p | {
809
+ input_ys .appendAssumeCapacity (try core .dhke .hashToCurve (p .secret .toBytes ()));
810
+ }
811
+
812
+ try self .localstore .value .addProofs (swap_request .inputs );
813
+ try self .checkYsSpendable (input_ys .items , .pending );
814
+
815
+ // Check that there are no duplicate proofs in request
816
+
817
+ {
818
+ var h = std .AutoHashMap (secp256k1 .PublicKey , void ).init (arena );
819
+
820
+ try h .ensureTotalCapacity (@intCast (input_ys .items .len ));
821
+
822
+ for (input_ys .items ) | i | {
823
+ if (h .fetchPutAssumeCapacity (i , {}) != null ) {
824
+ _ = try self .localstore .value .updateProofsStates (arena , input_ys .items , .unspent );
825
+ return error .DuplicateProofs ;
826
+ }
827
+ }
828
+ }
829
+
830
+ for (swap_request .inputs ) | proof | {
831
+ self .verifyProof (proof ) catch | err | {
832
+ std .log .info ("Error verifying proof in swap" , .{});
833
+ return err ;
834
+ };
835
+ }
836
+
837
+ var input_keyset_ids = std .AutoHashMap (nuts .Id , void ).init (arena );
838
+
839
+ try input_keyset_ids .ensureTotalCapacity (@intCast (swap_request .inputs .len ));
840
+
841
+ for (swap_request .inputs ) | p | input_keyset_ids .putAssumeCapacity (p .keyset_id , {});
842
+
843
+ var keyset_units = std .AutoHashMap (nuts .CurrencyUnit , void ).init (arena );
844
+
845
+ {
846
+ var it = input_keyset_ids .keyIterator ();
847
+
848
+ while (it .next ()) | id | {
849
+ const keyset = try self .localstore .value .getKeysetInfo (arena , id .* ) orelse {
850
+ std .log .debug ("Swap request with unknown keyset in inputs" , .{});
851
+ _ = try self .localstore .value .updateProofsStates (arena , input_ys .items , .unspent );
852
+ continue ;
853
+ };
854
+
855
+ try keyset_units .put (keyset .unit , {});
856
+ }
857
+ }
858
+
859
+ var output_keyset_ids = std .AutoHashMap (nuts .Id , void ).init (arena );
860
+
861
+ try output_keyset_ids .ensureTotalCapacity (@intCast (swap_request .outputs .len ));
862
+ for (swap_request .outputs ) | p | output_keyset_ids .putAssumeCapacity (p .keyset_id , {});
863
+
864
+ {
865
+ var it = output_keyset_ids .keyIterator ();
866
+ while (it .next ()) | id | {
867
+ const keyset = try self .localstore .value .getKeysetInfo (arena , id .* ) orelse {
868
+ std .log .debug ("Swap request with unknown keyset in outputs" , .{});
869
+ _ = try self .localstore .value .updateProofsStates (arena , input_ys .items , .unspent );
870
+ continue ;
871
+ };
872
+
873
+ keyset_units .putAssumeCapacity (keyset .unit , {});
874
+ }
875
+ }
876
+
877
+ // Check that all proofs are the same unit
878
+ // in the future it maybe possible to support multiple units but unsupported for
879
+ // now
880
+ if (keyset_units .count () > 1 ) {
881
+ std .log .err ("Only one unit is allowed in request: {any}" , .{keyset_units });
882
+
883
+ _ = try self .localstore
884
+ .value
885
+ .updateProofsStates (arena , input_ys .items , .unspent );
886
+
887
+ return error .MultipleUnits ;
888
+ }
889
+
890
+ var enforced_sig_flag = try core .nuts .nut11 .enforceSigFlag (arena , swap_request .inputs );
891
+
892
+ // let EnforceSigFlag {
893
+ // sig_flag,
894
+ // pubkeys,
895
+ // sigs_required,
896
+ // } = enforce_sig_flag(swap_request.inputs.clone());
897
+
898
+ if (enforced_sig_flag .sig_flag == .sig_all ) {
899
+ var _pubkeys = try std .ArrayList (secp256k1 .PublicKey ).initCapacity (arena , enforced_sig_flag .pubkeys .count ());
900
+
901
+ var it = enforced_sig_flag .pubkeys .keyIterator ();
902
+
903
+ while (it .next ()) | key | {
904
+ _pubkeys .appendAssumeCapacity (key .* );
905
+ }
906
+
907
+ for (swap_request .outputs ) | * blinded_message | {
908
+ nuts .nut11 .verifyP2pkBlindedMessages (blinded_message , _pubkeys .items , enforced_sig_flag .sigs_required ) catch | err | {
909
+ std .log .info ("Could not verify p2pk in swap request" , .{});
910
+ _ = try self .localstore
911
+ .value
912
+ .updateProofsStates (arena , input_ys .items , .unspent );
913
+ return err ;
914
+ };
915
+ }
916
+ }
917
+
918
+ var promises = try std .ArrayList (nuts .BlindSignature ).initCapacity (arena , swap_request .outputs .len );
919
+
920
+ for (swap_request .outputs ) | blinded_message | {
921
+ const blinded_signature = try self .blindSign (arena , blinded_message );
922
+ promises .appendAssumeCapacity (blinded_signature );
923
+ }
924
+
925
+ _ = try self .localstore
926
+ .value
927
+ .updateProofsStates (arena , input_ys .items , .spent );
928
+
929
+ try self .localstore
930
+ .value
931
+ .addBlindSignatures (
932
+ blinded_messages .items ,
933
+ promises .items ,
934
+ );
935
+
936
+ return .{
937
+ .signatures = promises .items ,
938
+ };
939
+ }
699
940
};
700
941
701
942
/// Generate new [`MintKeySetInfo`] from path
0 commit comments