diff --git a/lightspark-crypto-go/internal/lightspark_crypto.go b/lightspark-crypto-go/internal/lightspark_crypto.go index 911a093..8316852 100644 --- a/lightspark-crypto-go/internal/lightspark_crypto.go +++ b/lightspark-crypto-go/internal/lightspark_crypto.go @@ -409,6 +409,15 @@ func uniffiCheckChecksums() { panic("lightspark_crypto: uniffi_lightspark_crypto_checksum_func_sign_ecdsa: UniFFI API checksum mismatch") } } + { + checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { + return C.uniffi_lightspark_crypto_checksum_func_sign_transactions(uniffiStatus) + }) + if checksum != 40198 { + // If this happens try cleaning and rebuilding your project + panic("lightspark_crypto: uniffi_lightspark_crypto_checksum_func_sign_transactions: UniFFI API checksum mismatch") + } + } { checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t { return C.uniffi_lightspark_crypto_checksum_func_verify_ecdsa(uniffiStatus) @@ -1466,6 +1475,106 @@ func (_ FfiDestroyerTypeRemoteSigningResponse) Destroy(value RemoteSigningRespon value.Destroy() } +type Response struct { + CommitmentTx string + SweepTx string + HtlcInboundTx []StringTuple + HtlcOutboundTx []StringTuple + CounterpartySweepTx string + CounterpartyHtlcInboundTx []string + CounterpartyHtlcOutboundTx []string +} + +func (r *Response) Destroy() { + FfiDestroyerString{}.Destroy(r.CommitmentTx) + FfiDestroyerString{}.Destroy(r.SweepTx) + FfiDestroyerSequenceTypeStringTuple{}.Destroy(r.HtlcInboundTx) + FfiDestroyerSequenceTypeStringTuple{}.Destroy(r.HtlcOutboundTx) + FfiDestroyerString{}.Destroy(r.CounterpartySweepTx) + FfiDestroyerSequenceString{}.Destroy(r.CounterpartyHtlcInboundTx) + FfiDestroyerSequenceString{}.Destroy(r.CounterpartyHtlcOutboundTx) +} + +type FfiConverterTypeResponse struct{} + +var FfiConverterTypeResponseINSTANCE = FfiConverterTypeResponse{} + +func (c FfiConverterTypeResponse) Lift(rb RustBufferI) Response { + return LiftFromRustBuffer[Response](c, rb) +} + +func (c FfiConverterTypeResponse) Read(reader io.Reader) Response { + return Response{ + FfiConverterStringINSTANCE.Read(reader), + FfiConverterStringINSTANCE.Read(reader), + FfiConverterSequenceTypeStringTupleINSTANCE.Read(reader), + FfiConverterSequenceTypeStringTupleINSTANCE.Read(reader), + FfiConverterStringINSTANCE.Read(reader), + FfiConverterSequenceStringINSTANCE.Read(reader), + FfiConverterSequenceStringINSTANCE.Read(reader), + } +} + +func (c FfiConverterTypeResponse) Lower(value Response) RustBuffer { + return LowerIntoRustBuffer[Response](c, value) +} + +func (c FfiConverterTypeResponse) Write(writer io.Writer, value Response) { + FfiConverterStringINSTANCE.Write(writer, value.CommitmentTx) + FfiConverterStringINSTANCE.Write(writer, value.SweepTx) + FfiConverterSequenceTypeStringTupleINSTANCE.Write(writer, value.HtlcInboundTx) + FfiConverterSequenceTypeStringTupleINSTANCE.Write(writer, value.HtlcOutboundTx) + FfiConverterStringINSTANCE.Write(writer, value.CounterpartySweepTx) + FfiConverterSequenceStringINSTANCE.Write(writer, value.CounterpartyHtlcInboundTx) + FfiConverterSequenceStringINSTANCE.Write(writer, value.CounterpartyHtlcOutboundTx) +} + +type FfiDestroyerTypeResponse struct{} + +func (_ FfiDestroyerTypeResponse) Destroy(value Response) { + value.Destroy() +} + +type StringTuple struct { + First string + Second string +} + +func (r *StringTuple) Destroy() { + FfiDestroyerString{}.Destroy(r.First) + FfiDestroyerString{}.Destroy(r.Second) +} + +type FfiConverterTypeStringTuple struct{} + +var FfiConverterTypeStringTupleINSTANCE = FfiConverterTypeStringTuple{} + +func (c FfiConverterTypeStringTuple) Lift(rb RustBufferI) StringTuple { + return LiftFromRustBuffer[StringTuple](c, rb) +} + +func (c FfiConverterTypeStringTuple) Read(reader io.Reader) StringTuple { + return StringTuple{ + FfiConverterStringINSTANCE.Read(reader), + FfiConverterStringINSTANCE.Read(reader), + } +} + +func (c FfiConverterTypeStringTuple) Lower(value StringTuple) RustBuffer { + return LowerIntoRustBuffer[StringTuple](c, value) +} + +func (c FfiConverterTypeStringTuple) Write(writer io.Writer, value StringTuple) { + FfiConverterStringINSTANCE.Write(writer, value.First) + FfiConverterStringINSTANCE.Write(writer, value.Second) +} + +type FfiDestroyerTypeStringTuple struct{} + +func (_ FfiDestroyerTypeStringTuple) Destroy(value StringTuple) { + value.Destroy() +} + type CryptoError struct { err error } @@ -1627,6 +1736,85 @@ func (c FfiConverterTypeCryptoError) Write(writer io.Writer, value *CryptoError) } } +type FundsRecoveryKitError struct { + err error +} + +func (err FundsRecoveryKitError) Error() string { + return fmt.Sprintf("FundsRecoveryKitError: %s", err.err.Error()) +} + +func (err FundsRecoveryKitError) Unwrap() error { + return err.err +} + +// Err* are used for checking error type with `errors.Is` +var ErrFundsRecoveryKitErrorError = fmt.Errorf("FundsRecoveryKitErrorError") + +// Variant structs +type FundsRecoveryKitErrorError struct { + Message string +} + +func NewFundsRecoveryKitErrorError( + message string, +) *FundsRecoveryKitError { + return &FundsRecoveryKitError{ + err: &FundsRecoveryKitErrorError{ + Message: message, + }, + } +} + +func (err FundsRecoveryKitErrorError) Error() string { + return fmt.Sprint("Error", + ": ", + + "Message=", + err.Message, + ) +} + +func (self FundsRecoveryKitErrorError) Is(target error) bool { + return target == ErrFundsRecoveryKitErrorError +} + +type FfiConverterTypeFundsRecoveryKitError struct{} + +var FfiConverterTypeFundsRecoveryKitErrorINSTANCE = FfiConverterTypeFundsRecoveryKitError{} + +func (c FfiConverterTypeFundsRecoveryKitError) Lift(eb RustBufferI) error { + return LiftFromRustBuffer[error](c, eb) +} + +func (c FfiConverterTypeFundsRecoveryKitError) Lower(value *FundsRecoveryKitError) RustBuffer { + return LowerIntoRustBuffer[*FundsRecoveryKitError](c, value) +} + +func (c FfiConverterTypeFundsRecoveryKitError) Read(reader io.Reader) error { + errorID := readUint32(reader) + + switch errorID { + case 1: + return &FundsRecoveryKitError{&FundsRecoveryKitErrorError{ + Message: FfiConverterStringINSTANCE.Read(reader), + }} + default: + panic(fmt.Sprintf("Unknown error code %d in FfiConverterTypeFundsRecoveryKitError.Read()", errorID)) + } +} + +func (c FfiConverterTypeFundsRecoveryKitError) Write(writer io.Writer, value *FundsRecoveryKitError) { + switch variantValue := value.err.(type) { + case *FundsRecoveryKitErrorError: + writeInt32(writer, 1) + FfiConverterStringINSTANCE.Write(writer, variantValue.Message) + default: + _ = variantValue + panic(fmt.Sprintf("invalid error value `%v` in FfiConverterTypeFundsRecoveryKitError.Write", value)) + } +} + type LightsparkSignerError struct { err error } @@ -2224,6 +2412,92 @@ func (FfiDestroyerSequenceUint8) Destroy(sequence []uint8) { } } +type FfiConverterSequenceString struct{} + +var FfiConverterSequenceStringINSTANCE = FfiConverterSequenceString{} + +func (c FfiConverterSequenceString) Lift(rb RustBufferI) []string { + return LiftFromRustBuffer[[]string](c, rb) +} + +func (c FfiConverterSequenceString) Read(reader io.Reader) []string { + length := readInt32(reader) + if length == 0 { + return nil + } + result := make([]string, 0, length) + for i := int32(0); i < length; i++ { + result = append(result, FfiConverterStringINSTANCE.Read(reader)) + } + return result +} + +func (c FfiConverterSequenceString) Lower(value []string) RustBuffer { + return LowerIntoRustBuffer[[]string](c, value) +} + +func (c FfiConverterSequenceString) Write(writer io.Writer, value []string) { + if len(value) > math.MaxInt32 { + panic("[]string is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + for _, item := range value { + FfiConverterStringINSTANCE.Write(writer, item) + } +} + +type FfiDestroyerSequenceString struct{} + +func (FfiDestroyerSequenceString) Destroy(sequence []string) { + for _, value := range sequence { + FfiDestroyerString{}.Destroy(value) + } +} + +type FfiConverterSequenceTypeStringTuple struct{} + +var FfiConverterSequenceTypeStringTupleINSTANCE = FfiConverterSequenceTypeStringTuple{} + +func (c FfiConverterSequenceTypeStringTuple) Lift(rb RustBufferI) []StringTuple { + return LiftFromRustBuffer[[]StringTuple](c, rb) +} + +func (c FfiConverterSequenceTypeStringTuple) Read(reader io.Reader) []StringTuple { + length := readInt32(reader) + if length == 0 { + return nil + } + result := make([]StringTuple, 0, length) + for i := int32(0); i < length; i++ { + result = append(result, FfiConverterTypeStringTupleINSTANCE.Read(reader)) + } + return result +} + +func (c FfiConverterSequenceTypeStringTuple) Lower(value []StringTuple) RustBuffer { + return LowerIntoRustBuffer[[]StringTuple](c, value) +} + +func (c FfiConverterSequenceTypeStringTuple) Write(writer io.Writer, value []StringTuple) { + if len(value) > math.MaxInt32 { + panic("[]StringTuple is too large to fit into Int32") + } + + writeInt32(writer, int32(len(value))) + for _, item := range value { + FfiConverterTypeStringTupleINSTANCE.Write(writer, item) + } +} + +type FfiDestroyerSequenceTypeStringTuple struct{} + +func (FfiDestroyerSequenceTypeStringTuple) Destroy(sequence []StringTuple) { + for _, value := range sequence { + FfiDestroyerTypeStringTuple{}.Destroy(value) + } +} + func DecryptEcies(cipherText []uint8, privateKeyBytes []uint8) ([]uint8, error) { _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeCryptoError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { return C.uniffi_lightspark_crypto_fn_func_decrypt_ecies(FfiConverterSequenceUint8INSTANCE.Lower(cipherText), FfiConverterSequenceUint8INSTANCE.Lower(privateKeyBytes), _uniffiStatus) @@ -2308,6 +2582,18 @@ func SignEcdsa(msg []uint8, privateKeyBytes []uint8) ([]uint8, error) { } } +func SignTransactions(masterSeed string, data string, network Network) (Response, error) { + _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeFundsRecoveryKitError{}, func(_uniffiStatus *C.RustCallStatus) RustBufferI { + return C.uniffi_lightspark_crypto_fn_func_sign_transactions(FfiConverterStringINSTANCE.Lower(masterSeed), FfiConverterStringINSTANCE.Lower(data), FfiConverterTypeNetworkINSTANCE.Lower(network), _uniffiStatus) + }) + if _uniffiErr != nil { + var _uniffiDefaultValue Response + return _uniffiDefaultValue, _uniffiErr + } else { + return FfiConverterTypeResponseINSTANCE.Lift(_uniffiRV), _uniffiErr + } +} + func VerifyEcdsa(msg []uint8, signatureBytes []uint8, publicKeyBytes []uint8) (bool, error) { _uniffiRV, _uniffiErr := rustCallWithError(FfiConverterTypeCryptoError{}, func(_uniffiStatus *C.RustCallStatus) C.int8_t { return C.uniffi_lightspark_crypto_fn_func_verify_ecdsa(FfiConverterSequenceUint8INSTANCE.Lower(msg), FfiConverterSequenceUint8INSTANCE.Lower(signatureBytes), FfiConverterSequenceUint8INSTANCE.Lower(publicKeyBytes), _uniffiStatus) diff --git a/lightspark-crypto-go/internal/lightspark_crypto.h b/lightspark-crypto-go/internal/lightspark_crypto.h index db50cd7..6655d6e 100644 --- a/lightspark-crypto-go/internal/lightspark_crypto.h +++ b/lightspark-crypto-go/internal/lightspark_crypto.h @@ -292,6 +292,13 @@ RustBuffer uniffi_lightspark_crypto_fn_func_sign_ecdsa( RustCallStatus* out_status ); +RustBuffer uniffi_lightspark_crypto_fn_func_sign_transactions( + RustBuffer master_seed, + RustBuffer data, + RustBuffer network, + RustCallStatus* out_status +); + int8_t uniffi_lightspark_crypto_fn_func_verify_ecdsa( RustBuffer msg, RustBuffer signature_bytes, @@ -626,6 +633,10 @@ uint16_t uniffi_lightspark_crypto_checksum_func_sign_ecdsa( RustCallStatus* out_status ); +uint16_t uniffi_lightspark_crypto_checksum_func_sign_transactions( + RustCallStatus* out_status +); + uint16_t uniffi_lightspark_crypto_checksum_func_verify_ecdsa( RustCallStatus* out_status ); diff --git a/lightspark-crypto-go/libs/darwin/amd64/liblightspark_crypto.a b/lightspark-crypto-go/libs/darwin/amd64/liblightspark_crypto.a index 8bcf90a..4f69721 100644 Binary files a/lightspark-crypto-go/libs/darwin/amd64/liblightspark_crypto.a and b/lightspark-crypto-go/libs/darwin/amd64/liblightspark_crypto.a differ diff --git a/lightspark-crypto-go/libs/darwin/arm64/liblightspark_crypto.a b/lightspark-crypto-go/libs/darwin/arm64/liblightspark_crypto.a index b45f89b..0098a51 100644 Binary files a/lightspark-crypto-go/libs/darwin/arm64/liblightspark_crypto.a and b/lightspark-crypto-go/libs/darwin/arm64/liblightspark_crypto.a differ diff --git a/lightspark-crypto-go/libs/linux/amd64/liblightspark_crypto.a b/lightspark-crypto-go/libs/linux/amd64/liblightspark_crypto.a index 86f1342..229dfb4 100644 Binary files a/lightspark-crypto-go/libs/linux/amd64/liblightspark_crypto.a and b/lightspark-crypto-go/libs/linux/amd64/liblightspark_crypto.a differ diff --git a/lightspark-crypto-go/libs/linux/arm64/liblightspark_crypto.a b/lightspark-crypto-go/libs/linux/arm64/liblightspark_crypto.a index f5d5c58..45b9726 100644 Binary files a/lightspark-crypto-go/libs/linux/arm64/liblightspark_crypto.a and b/lightspark-crypto-go/libs/linux/arm64/liblightspark_crypto.a differ diff --git a/lightspark-crypto-go/signing.go b/lightspark-crypto-go/signing.go index d5d21e0..b63b8d9 100644 --- a/lightspark-crypto-go/signing.go +++ b/lightspark-crypto-go/signing.go @@ -203,16 +203,7 @@ func DecryptEcies(message []byte, privateKey []byte) ([]byte, error) { } func GenerateMultiSigAddress(network BitcoinNetwork, publicKey1 []byte, publicKey2 []byte) (string, error) { - var ffiNetwork internal.Network - - switch network { - case Mainnet: - ffiNetwork = internal.NetworkBitcoin - case Testnet: - ffiNetwork = internal.NetworkTestnet - case Regtest: - ffiNetwork = internal.NetworkRegtest - } + ffiNetwork := toInternalNetwork(network) return internal.GenerateMultisigAddress(ffiNetwork, publicKey1, publicKey2) } @@ -225,16 +216,61 @@ func getLightsparkSigner(seedBytes []byte, network BitcoinNetwork) (*internal.Li seed := internal.NewSeed(seedBytes) defer seed.Destroy() - var ffiNetwork internal.Network + ffiNetwork := toInternalNetwork(network) + + return internal.NewLightsparkSigner(seed, ffiNetwork) +} + +type Pair struct { + First string + Second string +} + +type FundsRecoveryResponse struct{ + CommitmentTx string + SweepTx string + HtlcInboundTx []Pair + HtlcOutboundTx []Pair + CounterpartyHtlcInboundTx []string + CounterpartyHtlcOutboundTx []string +} +func SignTransactions (masterSeed string, data string, network BitcoinNetwork) (*FundsRecoveryResponse, error) { + ffiNetwork := toInternalNetwork(network) + + resp, err := internal.SignTransactions(masterSeed, data, ffiNetwork) + if err != nil { + return nil, err + } + + return &FundsRecoveryResponse{ + CommitmentTx: resp.CommitmentTx, + SweepTx: resp.SweepTx, + HtlcInboundTx: toPairArray(resp.HtlcInboundTx), + HtlcOutboundTx: toPairArray(resp.HtlcOutboundTx), + CounterpartyHtlcInboundTx: resp.CounterpartyHtlcInboundTx, + CounterpartyHtlcOutboundTx: resp.CounterpartyHtlcOutboundTx, + }, nil + +} + +func toInternalNetwork(network BitcoinNetwork) internal.Network { switch network { case Mainnet: - ffiNetwork = internal.NetworkBitcoin + return internal.NetworkBitcoin case Testnet: - ffiNetwork = internal.NetworkTestnet + return internal.NetworkTestnet case Regtest: - ffiNetwork = internal.NetworkRegtest + return internal.NetworkRegtest + default: + return internal.NetworkBitcoin } +} - return internal.NewLightsparkSigner(seed, ffiNetwork) +func toPairArray(array []internal.StringTuple) []Pair { + var pairs []Pair + for _, pair := range array { + pairs = append(pairs, Pair{First: pair.First, Second: pair.Second}) + } + return pairs } diff --git a/lightspark-crypto-go/signing_test.go b/lightspark-crypto-go/signing_test.go index 4c8a515..6ba9039 100644 --- a/lightspark-crypto-go/signing_test.go +++ b/lightspark-crypto-go/signing_test.go @@ -69,3 +69,11 @@ func TestDeriveKeyAndSign(t *testing.T) { require.NoError(t, err, "Failed to sign message") require.Equal(t, "fagpGOb9o/E8g62yL6jV5wtpTVzJ7R4rh0Xt2Uw4fPVd1Q+2ZJbkSrRBRj0bvk1qTSiCvoiCfD5CMEHZL4fAlA==", base64.StdEncoding.EncodeToString(signedMessage)) } + +func TestSignTransactions(t *testing.T) { + data := `{"commitment_tx": "0200000000010187748372b3dfb3d47c2bb0a02848a327c77f5c982d036626c6de0a958af56069000000000025cee5800236ad03000000000016001415f586897037ded59858ccb1d24d4fbe7602692e90d00300000000002200204a30a8d4aba2d9d2e245052fc3566e1e18c49c1f364351481bfdd3e4d3a60da00400473044022066dbda605a766bc8143e15825c878b3b9444637b04678da42fe8f0c317c41fc70220482bb1a741497b14650c1e07ec4a00803855ff0f6a1434c88ae068f51567e94201004752210315496a93245ab6a7373b4e7297a30e2a20feefc50c59484dce93b6685e3ec0302103e65fcacf66816c0cb7d85726944463efe1466184a01d99949aed8a8b0676009a52aee1158720", "sweep_tx": "020000000001016d0d0c47799e62541fc4bb51461b4bed8a5ed978ebe4f52d4c168a5b950d6f5401000000009000000001fbb80300000000001600146b0009af85b18052eb83afbdc9c45521c552588f0300004d632102a299258a6ac6b9be6b7ee879a87aca8a30e05d15e915b7af722f09d44c5014a867029000b2752103c74ec665bd1547f4a3dccee02c104677c7e880c6b0bdaec0cea195680d3cb62768ac00000000", "htlc_tx": [], "serialized_htlc_sweep_tx": [], "channel_point": "6960f58a950adec62666032d985c7fc727a34828a0b02b7cd4b3dfb372837487:0", "sweep_tx_add_tweak": "201d490866cdcc50199497d98b699f4ae367b23e801ffe405f3f983deef42f56", "htlc_tx_add_tweak": "146d304968ba398899c7147fb641a6e20d4134b2c78abf4a2eb67e094fd730c1", "funding_private_key_derivation_path": "m/3/599143572/0", "delayed_payment_base_key_derivation_path": "m/3/599143572/3", "htlc_base_key_derivation_path": "m/3/599143572/4", "channel_capacity": 500000, "nonces": [], "commitment_number": 1}` + seed := "f520e5271623fe21c76b0212f855c97a" + + _, err := SignTransactions(seed, data, Regtest) + require.NoError(t, err, "Failed to sign transactions") +}