sodot
import "github.com/sodot-rs/sodot-go-sdk"
Sodot Go module provides the ability to run TSS protocols for both ECDSA and Ed25519.
Index
- Constants
- type BIP340
- func NewBIP340(hostUrl string) BIP340
- func (bip340 BIP340) CreateRoom(numParties uint16, apiKey string) (RoomUUID, error)
- func (bip340 BIP340) DerivePrivateKeyFromXpriv(xpriv string, derivationPath []uint32) (PrivateKey, error)
- func (bip340 BIP340) DeriveTweakPubkey(share BIP340SecretShare, derivationPath []uint32, tweak *[32]byte) (BIP340PublicKey, error)
- func (bip340 BIP340) DeriveTweakPubkeyFromXpub(xpub string, derivationPath []uint32, tweak *[32]byte) (BIP340PublicKey, error)
- func (bip340 BIP340) ExportFullPrivateKey(roomUuid RoomUUID, share BIP340SecretShare, toExportID KeygenID) (string, error)
- func (bip340 BIP340) ExportID(share BIP340SecretShare) (KeygenID, error)
- func (bip340 BIP340) GetXpub(share BIP340SecretShare) (string, error)
- func (bip340 BIP340) ImportPrivateKeyImporter(roomUuid RoomUUID, threshold uint16, privateKey PrivateKey, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (BIP340SecretShare, BIP340PublicKey, error)
- func (bip340 BIP340) ImportPrivateKeyRecipient(roomUuid RoomUUID, threshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (BIP340SecretShare, BIP340PublicKey, error)
- func (bip340 BIP340) InitKeygen() (KeygenID, KeygenPrivateKey, error)
- func (bip340 BIP340) Keygen(roomUuid RoomUUID, numParties uint16, threshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (BIP340SecretShare, BIP340PublicKey, error)
- func (bip340 BIP340) OfflineExportFullPrivateKey(shares []BIP340SecretShare) (string, error)
- func (bip340 BIP340) Refresh(roomUuid RoomUUID, share BIP340SecretShare) (BIP340SecretShare, BIP340PublicKey, error)
- func (bip340 BIP340) ReshareNewParty(roomUuid RoomUUID, newThreshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (BIP340SecretShare, BIP340PublicKey, error)
- func (bip340 BIP340) ReshareRemainingParty(roomUuid RoomUUID, newThreshold uint16, share BIP340SecretShare, keygenIDs []KeygenID) (BIP340SecretShare, BIP340PublicKey, error)
- func (bip340 BIP340) Sign(roomUuid RoomUUID, share BIP340SecretShare, msg []byte, derivationPath []uint32, tweak *[32]byte) (BIP340Signature, error)
- type BIP340PublicKey
- type BIP340SecretShare
- type BIP340Signature
- type Ecdsa
- func NewEcdsa(hostUrl string) Ecdsa
- func (ecdsa Ecdsa) CreateRoom(numParties uint16, apiKey string) (RoomUUID, error)
- func (ecdsa Ecdsa) DerivePrivateKeyFromXpriv(xpriv string, derivationPath []uint32) (PrivateKey, error)
- func (ecdsa Ecdsa) DerivePubkey(share EcdsaSecretShare, derivationPath []uint32) (EcdsaPublicKey, error)
- func (ecdsa Ecdsa) DerivePubkeyFromXpub(xpub string, derivationPath []uint32) (EcdsaPublicKey, error)
- func (ecdsa Ecdsa) ExportFullPrivateKey(roomUuid RoomUUID, share EcdsaSecretShare, toExportID KeygenID) (string, error)
- func (ecdsa Ecdsa) ExportID(share EcdsaSecretShare) (KeygenID, error)
- func (ecda Ecdsa) GetXpub(share EcdsaSecretShare) (string, error)
- func (ecdsa Ecdsa) ImportPrivateKeyImporter(roomUuid RoomUUID, threshold uint16, privateKey PrivateKey, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (EcdsaSecretShare, EcdsaPublicKey, error)
- func (ecdsa Ecdsa) ImportPrivateKeyRecipient(roomUuid RoomUUID, threshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (EcdsaSecretShare, EcdsaPublicKey, error)
- func (ecdsa Ecdsa) InitKeygen() (KeygenID, KeygenPrivateKey, error)
- func (ecdsa Ecdsa) Keygen(roomUuid RoomUUID, numParties uint16, threshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (EcdsaSecretShare, EcdsaPublicKey, error)
- func (ecdsa Ecdsa) OfflineExportFullPrivateKey(shares []EcdsaSecretShare) (string, error)
- func (ecdsa Ecdsa) Refresh(roomUuid RoomUUID, share EcdsaSecretShare) (EcdsaSecretShare, EcdsaPublicKey, error)
- func (ecdsa Ecdsa) ReshareNewParty(roomUuid RoomUUID, newThreshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (EcdsaSecretShare, EcdsaPublicKey, error)
- func (ecdsa Ecdsa) ReshareRemainingParty(roomUuid RoomUUID, newThreshold uint16, share EcdsaSecretShare, keygenIDs []KeygenID) (EcdsaSecretShare, EcdsaPublicKey, error)
- func (ecdsa Ecdsa) Sign(roomUuid RoomUUID, share EcdsaSecretShare, msgHash MessageHash, derivationPath []uint32) (EcdsaSignature, error)
- type EcdsaPublicKey
- func EcdsaPublicKeyFromString(s string) (EcdsaPublicKey, error)
- func (pubkey *EcdsaPublicKey) Equal(other EcdsaPublicKey) bool
- func (pubkey *EcdsaPublicKey) SerializeCompressed() (ret [33]byte)
- func (pubkey *EcdsaPublicKey) SerializeUncompressed() [65]byte
- func (pubkey *EcdsaPublicKey) String() string
- type EcdsaSecretShare
- type EcdsaSignature
- type Ed25519
- func NewEd25519(hostUrl string) Ed25519
- func (ed25519 Ed25519) CreateRoom(numParties uint16, apiKey string) (RoomUUID, error)
- func (ed25519 Ed25519) DerivePrivateKeyFromSpriv(spriv string, derivationPath []uint32) (PrivateKey, error)
- func (ed25519 Ed25519) DerivePubkey(share Ed25519SecretShare, derivationPath []uint32) (Ed25519PublicKey, error)
- func (ed25519 Ed25519) DerivePubkeyFromSpub(spub string, derivationPath []uint32) (Ed25519PublicKey, error)
- func (ed25519 Ed25519) ExportFullPrivateKey(roomUuid RoomUUID, share Ed25519SecretShare, toExportID KeygenID) (string, error)
- func (ed25519 Ed25519) ExportID(share Ed25519SecretShare) (KeygenID, error)
- func (ed25519 Ed25519) GetSpub(share Ed25519SecretShare) (string, error)
- func (ed25519 Ed25519) ImportPrivateKeyImporter(roomUuid RoomUUID, threshold uint16, privateKey PrivateKey, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID, optionalIsPrivateKeyRaw ...bool) (Ed25519SecretShare, Ed25519PublicKey, error)
- func (ed25519 Ed25519) ImportPrivateKeyRecipient(roomUuid RoomUUID, threshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (Ed25519SecretShare, Ed25519PublicKey, error)
- func (ed25519 Ed25519) InitKeygen() (KeygenID, KeygenPrivateKey, error)
- func (ed25519 Ed25519) Keygen(roomUuid RoomUUID, numParties uint16, threshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (Ed25519SecretShare, Ed25519PublicKey, error)
- func (ed25519 Ed25519) OfflineExportFullPrivateKey(shares []Ed25519SecretShare) (string, error)
- func (ed25519 Ed25519) Refresh(roomUuid RoomUUID, share Ed25519SecretShare) (Ed25519SecretShare, Ed25519PublicKey, error)
- func (ed25519 Ed25519) ReshareNewParty(roomUuid RoomUUID, newThreshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (Ed25519SecretShare, Ed25519PublicKey, error)
- func (ed25519 Ed25519) ReshareRemainingParty(roomUuid RoomUUID, newThreshold uint16, share Ed25519SecretShare, keygenIDs []KeygenID) (Ed25519SecretShare, Ed25519PublicKey, error)
- func (ed25519 Ed25519) Sign(roomUuid RoomUUID, share Ed25519SecretShare, msg []byte, derivationPath []uint32) (Ed25519Signature, error)
- type Ed25519PublicKey
- type Ed25519SecretShare
- type Ed25519Signature
- type KeygenID
- type KeygenPrivateKey
- type MessageHash
- type PrivateKey
- type RoomUUID
Constants
const SODOT_RELAY_URL = "us1.sodot.dev"
type BIP340
Class providing the functionality for the BIP340 protocol via the FROST MPC protocol.
type BIP340 struct {
HostUrl string
}
func NewBIP340
func NewBIP340(hostUrl string) BIP340
NewBIP340 creates a new BIP340 instance
host URL to use, if "" is passed, the default host url(SODOT_RELAY_URL
) will be used
func (BIP340) CreateRoom
func (bip340 BIP340) CreateRoom(numParties uint16, apiKey string) (RoomUUID, error)
Creates a room for the given number of parties. A room is a one time instance used to perform a single MPC operation(keygen/signing/refresh etc.) between parties.
This function should be called in the backend so to not embed the API key in code that is distributed to the users. After the backend calls this function, the other parties can join the room by calling the relevant keygen/signing/refresh/etc. operation.
Returns a RoomUUID of the created room.
func (BIP340) DerivePrivateKeyFromXpriv
func (bip340 BIP340) DerivePrivateKeyFromXpriv(xpriv string, derivationPath []uint32) (PrivateKey, error)
Parses an xpriv
string according to BIP-32 non-hardened, and returns the derived private key for a given BIP-32 non-hardened derivation path xpriv
must be a valid secp256k1 xpriv string.
func (BIP340) DeriveTweakPubkey
func (bip340 BIP340) DeriveTweakPubkey(share BIP340SecretShare, derivationPath []uint32, tweak *[32]byte) (BIP340PublicKey, error)
Returns the (optionally)derived and (optionally)tweaked public key for a keygenResult
for a given BIP-32 non-hardened derivation path and an optional BIP-341 tweak
The tweak
is used to tweak a public key with a BIP-341 Taproot tweaking, by supplying the T
value from the BIP-341 Taproot specification(https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#script-validation-rules\):
Let t = hash_{TapTweak}(p || km).
If t ≥ 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141 (order of secp256k1), fail.
Let Q = P + int(t)G.
func (BIP340) DeriveTweakPubkeyFromXpub
func (bip340 BIP340) DeriveTweakPubkeyFromXpub(xpub string, derivationPath []uint32, tweak *[32]byte) (BIP340PublicKey, error)
DeriveTweakPubkeyFromXpub derives a BIP340 public key from an extended public key (Xpub) using the provided derivation path and an optional 32-byte tweak. Returns the derived public key or an error on failure.
func (BIP340) ExportFullPrivateKey
func (bip340 BIP340) ExportFullPrivateKey(roomUuid RoomUUID, share BIP340SecretShare, toExportID KeygenID) (string, error)
Combine all BIP340SecretShare-s and export the full private key to a single party. Requires a threshold amount of parties to participate. toExportID
specifies the KeygenID of the party that should receive the private key, all parties must provide the same KeygenID or else export will fail.
The party being exported to will receive a string
containing the full xpriv
, while the rest will receive an empty string.
func (BIP340) ExportID
func (bip340 BIP340) ExportID(share BIP340SecretShare) (KeygenID, error)
Extracts the KeygenID from an BIP340SecretShare. Useful for reshare operations.
func (BIP340) GetXpub
func (bip340 BIP340) GetXpub(share BIP340SecretShare) (string, error)
GetXpub returns a base58 encoded extended public key (Xpub, See [BIP-32] for more details) derived from a BIP340SecretShare. The Xpub can be used either via third party libraries or via BIP340.DeriveTweakPubkeyFromXpub [BIP-32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format
func (BIP340) ImportPrivateKeyImporter
func (bip340 BIP340) ImportPrivateKeyImporter(roomUuid RoomUUID, threshold uint16, privateKey PrivateKey, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (BIP340SecretShare, BIP340PublicKey, error)
WARNING: Private key import is an advanced feature of the SDK. We strongly advise consulting with the Sodot team before using it, due to a full private key being imported from a different system. Secret shares generated from imported private keys will always have the risk of the private key having been compromised in the past or in the future in case the private key is not deleted after the import operation.
Importing a full private key, and sharing into a T-of-N
sharing, the resultant key shares will be of the exact same public key as the full private key.
This is the method that an importing party (meaning one the party in possesion of the private key) should use for receiving a key share in the new T-of-N
quorum.
NOTE: optionalIsPrivateKeyRaw
is an optional parameter with a default value of false. It should be set to true if privateKey
is a raw private key and not an RFC 8032 encoded secret key. This will only be the case when manually creating this private key data, in case of importing from a standard BIP340 system this won't be the case and can be left as the default false value.
Returns an BIP340SecretShare with the secret data that can be used for signing as well as the BIP340PublicKey.
func (BIP340) ImportPrivateKeyRecipient
func (bip340 BIP340) ImportPrivateKeyRecipient(roomUuid RoomUUID, threshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (BIP340SecretShare, BIP340PublicKey, error)
WARNING: Private key import is an advanced feature of the SDK. We strongly advise consulting with the Sodot team before using it, due to a full private key being imported from a different system. Secret shares generated from imported private keys will always have the risk of the private key having been compromised in the past or in the future in case the private key is not deleted after the import operation.
Importing a full private key, and sharing into a T-of-N
sharing, the resultant key shares will be of the exact same public key as the full private key.
This is the method that a new party (meaning one that does not currently have the private key) should use for receiving a key share in the new T-of-N
quorum. The method takes the same input parameters as BIP340.Keygen since for a new party joining the quorum the Import
operation is very similar to a Keygen
operation.
Returns an BIP340SecretShare with the secret data that can be used for signing as well as the BIP340PublicKey.
func (BIP340) InitKeygen
func (bip340 BIP340) InitKeygen() (KeygenID, KeygenPrivateKey, error)
All parties must call this function before calling Keygen. All parties receive a (KeygenID, KeygenPrivateKey) as an output from this function. The KeygenID must be sent through an authenticated communication channel to all other devices we wish to perform BIP340.Keygen with. Once we have the KeygenID-s of all parties, then BIP340.Keygen can be called with the same KeygenPrivateKey as was returned here.
func (BIP340) Keygen
func (bip340 BIP340) Keygen(roomUuid RoomUUID, numParties uint16, threshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (BIP340SecretShare, BIP340PublicKey, error)
Generate a keypair for the given number of parties and threshold.
Requires configuring the threshold
and total number of participants (numParties
) who will hold a secret share. This party's KeygenPrivateKey must be provided as well as an array of KeygenID-s (created using BIP340.InitKeygen) received from all other parties we wish to share the keypair with.
Returns an BIP340PublicKey and an BIP340SecretShare that contains the public key as well as the secret data that can be used for signing.
func (BIP340) OfflineExportFullPrivateKey
func (bip340 BIP340) OfflineExportFullPrivateKey(shares []BIP340SecretShare) (string, error)
Receives as input an array of threshold
BIP340SecretShare-s and locally computes the full private key (xpriv
). The main use case for this function is in an offline recovery setting where BIP340SecretShare-s are collected manually and used to recover the full private key on an air-gapped server/device.
Returns a string containing the full xpriv
.
func (BIP340) Refresh
func (bip340 BIP340) Refresh(roomUuid RoomUUID, share BIP340SecretShare) (BIP340SecretShare, BIP340PublicKey, error)
Used for refreshing the secret material of all parties without altering the public key at all. Takes an BIP340SecretShare as input and returns a new one for the same public key but with fresh key material. Be careful to delete the BIP340SecretShare given as input before it is certain that all devices have properly stored the fresh BIP340SecretShare that is output. Note that the new BIP340SecretShare-s may only be used with each other, attempting to use older BIP340SecretShare-s with newer ones for signing will result in failure.
The motivation for using refresh is to enhance security by switching the secret key material frequently, this means that an adversary will need to compromise multiple devices at the same time in order to compromise the private key.
func (BIP340) ReshareNewParty
func (bip340 BIP340) ReshareNewParty(roomUuid RoomUUID, newThreshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (BIP340SecretShare, BIP340PublicKey, error)
WARNING: Key resharing is an advanced feature of the SDK. We strongly advise consulting with the Sodot team before using it, as incorrect usage might lead to the detriment of the private key security. To use the feature correctly, developers using this feature must make sure that at least n - t + 1
parties of the t-of-n
signing quorum delete their current shares before using the resharing of the private key. Also, after resharing, the resharing operation must not be considered complete until such deletion has occurred. Since deleting a share cannot be guaranteed cryptographically, it must be guaranteed by the software architecture (hence, by the developers using the SDK).
Resharing the private key of the t-of-n
quorum of signers, the resultant key shares will be of the exact same public key as the previous quorum. Resharing should be used in cases where we aim to modify the current t-of-n
quorum with a new quorum with newT-of-newN
signers for the same public key.
This is the method that a new party (meaning one that does not currently have an BIP340SecretShare ) should use for receiving a key share in the new newT-of-newN
quorum. The method takes the same input parameters as BIP340.Keygen since for a new party joining the quorum the Reshare
operation is very similar to a Keygen
operation. In order to receive the KeygenID-s of parties that are already a part of the quorum, those parties will need to call [ExportID] and send the result to the parties in the new quorum.
Returns an BIP340SecretShare with the secret data that can be used for signing as well as the BIP340PublicKey.
func (BIP340) ReshareRemainingParty
func (bip340 BIP340) ReshareRemainingParty(roomUuid RoomUUID, newThreshold uint16, share BIP340SecretShare, keygenIDs []KeygenID) (BIP340SecretShare, BIP340PublicKey, error)
WARNING: Key resharing is an advanced feature of the SDK. We strongly advise consulting with the Sodot team before using it, as incorrect usage might lead to the detriment of the private key security. To use the feature correctly, developers using this feature must make sure that at least n - t + 1
parties of the t-of-n
signing quorum delete their current shares before using the resharing of the private key. Also, after resharing, the resharing operation must not be considered complete until such deletion has occurred. Since deleting a share cannot be guaranteed cryptographically, it must be guaranteed by the software architecture (hence, by the developers using the SDK).
Resharing the private key of the t-of-n
quorum of signers, the resultant key shares will be of the exact same public key as the previous quorum. Resharing should be used in cases where we aim to modify the current t-of-n
quorum with a new quorum with newT-of-newN
signers for the same public key.
This is the method that a remaining party (meaning one that does currently have a key share) should use for receiving a new key share in the new newT-of-newN
quorum. The method takes the same input parameters as BIP340.ReshareNewParty except that it will use its existing BIP340SecretShare instead of a new KeygenPrivateKey. In order to receive the KeygenID-s of parties that are already a part of the quorum, those parties will need to call [ExportID] and send the result to the parties in the new quorum.
Returns a new BIP340SecretShare with the secret data that can be used for signing as well as the BIP340PublicKey.
func (BIP340) Sign
func (bip340 BIP340) Sign(roomUuid RoomUUID, share BIP340SecretShare, msg []byte, derivationPath []uint32, tweak *[32]byte) (BIP340Signature, error)
Sign a message with an BIP340SecretShare created using BIP340.Keygen. derivationPath
must be a BIP-32 non-hardened derivation path to use for signing msg
. tweak
is an optional parameter to support BIP341 taproot tweaks, see BIP340.DeriveTweakPubkey.
Returns the signature of the message.
type BIP340PublicKey
An BIP340 public key, generated by the BIP340.Keygen function.
type BIP340PublicKey [32]byte
func BIP340PublicKeyFromString
func BIP340PublicKeyFromString(s string) (BIP340PublicKey, error)
BIP340PublicKeyFromString parses a hex string into an BIP340PublicKey.
func (*BIP340PublicKey) String
func (pubkey *BIP340PublicKey) String() string
String returns the hex representation of the public key.
type BIP340SecretShare
The per-party private key material that is used for threshold signing
type BIP340SecretShare string
func (BIP340SecretShare) Bytes
func (share BIP340SecretShare) Bytes() []byte
Bytes returns the raw representation of the BIP340SecretShare.
type BIP340Signature
An BIP340 signature, generated by the BIP340.Sign function.
type BIP340Signature [64]byte
func (*BIP340Signature) String
func (sig *BIP340Signature) String() string
String returns the hex representation of the signature.
type Ecdsa
Class providing the functionality for the ECDSA protocol via the DKLs19 MPC protocol.
type Ecdsa struct {
HostUrl string
}
Example
const N = 3
const T = 2
const API_KEY = "MY_API_KEY"
ecdsa := NewEcdsa("")
keygenRoomUuid, err := ecdsa.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// All parties call initKeygen to get a KeygenID and a KeygenPrivateKey
keygenID, keygenPrivKey, err := ecdsa.InitKeygen()
if err != nil {
panic(err)
}
// Send the keygenID to all other parties
_ = keygenID
// All parties receive the keygenIds from all other parties
keygenIds := []KeygenID{"keygenID1", "keygenID2"}
// All parties join the keygen room
secretShare, pk, err := ecdsa.Keygen(keygenRoomUuid, N, T, keygenPrivKey, keygenIds)
if err != nil {
panic(err)
}
// The public key can now be used to verify signatures
_ = pk
// Pick the derivation path of the public key you want to sign for
derivationPath := []uint32{44, 60, 0, 0, 0}
// Get the public key for the derivation path
derivedPubKey, err := ecdsa.DerivePubkey(secretShare, derivationPath)
if err != nil {
panic(err)
}
// The derived public key can now be used to verify signatures with the same derivation path
_ = derivedPubKey
// To sign a message, create a signing room on the server side, using your API_KEY
signingRoomUuid, err := ecdsa.CreateRoom(T, API_KEY)
if err != nil {
panic(err)
}
// Hash the message
messageHash := MessageHashFromSha256([]byte("my message"))
// 2 parties join the signing room
signature, err := ecdsa.Sign(signingRoomUuid, secretShare, messageHash, derivationPath)
if err != nil {
panic(err)
}
// This signature can now be verified against pubkey
_ = signature
// Refreshing the secret key material
// Your server creates a room for 3 parties
refreshRoomUuid, err := ecdsa.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// All parties join the refresh room
// Note: the public key returned here is the same as the one returned by the keygen
refreshedShare, _, err := ecdsa.Refresh(refreshRoomUuid, secretShare)
if err != nil {
panic(err)
}
// Signing using the new secret key material
// The room is again created by the server
signingRoomUuid2, err := ecdsa.CreateRoom(T, API_KEY)
if err != nil {
panic(err)
}
messageHash2 := MessageHashFromSha256([]byte("my new message"))
signature2, err := ecdsa.Sign(signingRoomUuid2, refreshedShare, messageHash2, derivationPath)
if err != nil {
panic(err)
}
// This signature can now be verified against pubkey
_ = signature2
func NewEcdsa
func NewEcdsa(hostUrl string) Ecdsa
NewEcdsa creates a new Ecdsa instance
host URL to use, if "" is passed, the default host url(SODOT_RELAY_URL
) will be used
func (Ecdsa) CreateRoom
func (ecdsa Ecdsa) CreateRoom(numParties uint16, apiKey string) (RoomUUID, error)
Creates a room for the given number of parties. A room is a one time instance used to perform a single MPC operation(keygen/signing/refresh etc.) between parties.
This function should be called in the backend so to not embed the API key in code that is distributed to the users. After the backend calls this function, the other parties can join the room by calling the relevant keygen/signing/refresh/etc. operation.
Returns a RoomUUID of the created room.
func (Ecdsa) DerivePrivateKeyFromXpriv
func (ecdsa Ecdsa) DerivePrivateKeyFromXpriv(xpriv string, derivationPath []uint32) (PrivateKey, error)
Parses an xpriv
string according to BIP-32 non-hardened, and returns the derived private key for a given BIP-32 non-hardened derivation path xpriv
must be a valid secp256k1 xpriv string.
func (Ecdsa) DerivePubkey
func (ecdsa Ecdsa) DerivePubkey(share EcdsaSecretShare, derivationPath []uint32) (EcdsaPublicKey, error)
Returns the derived public key for a EcdsaSecretShare for a given BIP-32 non-hardened derivation path.
func (Ecdsa) DerivePubkeyFromXpub
func (ecdsa Ecdsa) DerivePubkeyFromXpub(xpub string, derivationPath []uint32) (EcdsaPublicKey, error)
DerivePubkeyFromXpub derives an Ecdsa public key from the given extended public key (Xpub) using the specified derivation path. Returns the derived public key or an error on failure.
func (Ecdsa) ExportFullPrivateKey
func (ecdsa Ecdsa) ExportFullPrivateKey(roomUuid RoomUUID, share EcdsaSecretShare, toExportID KeygenID) (string, error)
Combine all EcdsaSecretShare-s and export the full private key to a single party. Requires a threshold amount of parties to participate. toExportID
specifies the KeygenID of the party that should receive the private key, all parties must provide the same KeygenID or else export will fail.
The party being exported to will receive a string
containing the full xpriv
, while the rest will receive an empty string.
func (Ecdsa) ExportID
func (ecdsa Ecdsa) ExportID(share EcdsaSecretShare) (KeygenID, error)
Extracts the KeygenID from an EcdsaSecretShare. Useful for reshare operations.
func (Ecdsa) GetXpub
func (ecda Ecdsa) GetXpub(share EcdsaSecretShare) (string, error)
GetXpub returns a base58 encoded extended public key (Xpub, See [BIP-32] for more details) derived from a EcdsaSecretShare. The Xpub can be used either via third party libraries or via Ecdsa.DerivePubkeyFromXpub [BIP-32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format
func (Ecdsa) ImportPrivateKeyImporter
func (ecdsa Ecdsa) ImportPrivateKeyImporter(roomUuid RoomUUID, threshold uint16, privateKey PrivateKey, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (EcdsaSecretShare, EcdsaPublicKey, error)
WARNING: Private key import is an advanced feature of the SDK. We strongly advise consulting with the Sodot team before using it, due to a full private key being imported from a different system. Secret shares generated from imported private keys will always have the risk of the private key having been compromised in the past or in the future in case the private key is not deleted after the import operation.
Importing a full private key, and sharing into a T-of-N
sharing, the resultant key shares will be of the exact same public key as the full private key.
This is the method that an importing party (meaning one the party in possesion of the private key) should use for receiving a key share in the new T-of-N
quorum.
Returns an EcdsaSecretShare with the secret data that can be used for signing as well as the EcdsaPublicKey.
Example
const N = 3
const T = 2
const API_KEY = "MY_API_KEY"
// A private key is created in some external system.
privateKey := PrivateKey("8b7683659ee5ba5f0e1329e290ea9a9f051794535df627739717a600a10bff92")
// Some time passes...
// Now this party wishes to be part of a new quorum of `2-of-3` sharing of the private key.
//
// The app server creates a room for N(= 3) parties.
ecdsa := NewEcdsa("")
importRoomUuid, err := ecdsa.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// The other parties must join the import room using the `ImportPrivateKeyRecipient` method.
keygenID, keygenPrivKey, err := ecdsa.InitKeygen()
if err != nil {
panic(err)
}
// This importing party will send its `KeygenID` to all other parties.
// This party will also receive the `KeygenID`s of all other parties of the new quorum.
keygenIds := []KeygenID{"keygenID1", keygenID, "keygenID3"} // Note that here we must include our own `KeygenID` as well, the order of the ids doesn't matter.
// Note: the public key returned here is the same as the public key of the imported key
newShare, _, err := ecdsa.ImportPrivateKeyImporter(importRoomUuid, T, privateKey, keygenPrivKey, keygenIds)
if err != nil {
panic(err)
}
// newShare can now be used for signing under the T(= 2) threshold with the same public key
_ = newShare
func (Ecdsa) ImportPrivateKeyRecipient
func (ecdsa Ecdsa) ImportPrivateKeyRecipient(roomUuid RoomUUID, threshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (EcdsaSecretShare, EcdsaPublicKey, error)
WARNING: Private key import is an advanced feature of the SDK. We strongly advise consulting with the Sodot team before using it, due to a full private key being imported from a different system. Secret shares generated from imported private keys will always have the risk of the private key having been compromised in the past or in the future in case the private key is not deleted after the import operation.
Importing a full private key, and sharing into a T-of-N
sharing, the resultant key shares will be of the exact same public key as the full private key.
This is the method that a new party (meaning one that does not currently have the private key) should use for receiving a key share in the new T-of-N
quorum. The method takes the same input parameters as Ecdsa.Keygen since for a new party joining the quorum the Import
operation is very similar to a Keygen
operation.
Returns an EcdsaSecretShare with the secret data that can be used for signing as well as the EcdsaPublicKey.
Example
const N = 3
const T = 2
const API_KEY = "MY_API_KEY"
// A private key is created in some external system.
// Some time passes...
// Now this party wishes to be part of a new quorum of `2-of-3` sharing of the private key.
//
// The app server creates a room for N(= 3) parties.
ecdsa := NewEcdsa("")
importRoomUuid, err := ecdsa.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// The party with the private key must join the import room using the `ImportPrivateKeyImporter` method.
keygenID, keygenPrivKey, err := ecdsa.InitKeygen()
if err != nil {
panic(err)
}
// This new party will send its `KeygenID` to all other parties.
// This party will also receive the `KeygenID`s of all other parties of the new quorum.
keygenIds := []KeygenID{"keygenID1", keygenID, "keygenID3"} // Note that here we must include our own `KeygenID` as well, the order of the ids doesn't matter.
// Note: the public key returned here is the same as the public key of the imported key
newShare, _, err := ecdsa.ImportPrivateKeyRecipient(importRoomUuid, T, keygenPrivKey, keygenIds)
if err != nil {
panic(err)
}
// newShare can now be used for signing under the T(= 2) threshold with the same public key
_ = newShare
func (Ecdsa) InitKeygen
func (ecdsa Ecdsa) InitKeygen() (KeygenID, KeygenPrivateKey, error)
All parties must call this function before calling Keygen. All parties receive a (KeygenID, KeygenPrivateKey) as an output from this function. The KeygenID must be sent through an authenticated communication channel to all other devices we wish to perform Ecdsa.Keygen with. Once we have the KeygenID-s of all parties, then Ecdsa.Keygen can be called with the same KeygenPrivateKey as was returned here.
func (Ecdsa) Keygen
func (ecdsa Ecdsa) Keygen(roomUuid RoomUUID, numParties uint16, threshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (EcdsaSecretShare, EcdsaPublicKey, error)
Generate a keypair for the given number of parties and threshold.
Requires configuring the threshold
and total number of participants (numParties
) who will hold a secret share. This party's KeygenPrivateKey must be provided as well as an array of KeygenID-s (created using Ecdsa.InitKeygen) received from all other parties we wish to share the keypair with.
Returns a EcdsaSecretShare and an EcdsaPublicKey that contains the public key as well as the secret data that can be used for signing.
Example
// Your server side creates a room for 3 parties using its API_KEY
// Creating a room uuid should always happen on the server side using your API_KEY
const N = 3
const T = 2
const API_KEY = "MY_API_KEY"
ecdsa := NewEcdsa("")
keygenRoomUuid, err := ecdsa.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// All parties call initKeygen to get a KeygenID and a KeygenPrivateKey
keygenID, keygenPrivKey, err := ecdsa.InitKeygen()
if err != nil {
panic(err)
}
// Send the keygenID to all other parties
_ = keygenID
// All parties receive the keygenIds from all other parties
keygenIds := []KeygenID{"keygenID1", "keygenID2"}
// All parties join the keygen room
secretShare, pk, err := ecdsa.Keygen(keygenRoomUuid, N, T, keygenPrivKey, keygenIds)
if err != nil {
panic(err)
}
// The public key can now be used to verify signatures
_, _ = secretShare, pk
func (Ecdsa) OfflineExportFullPrivateKey
func (ecdsa Ecdsa) OfflineExportFullPrivateKey(shares []EcdsaSecretShare) (string, error)
Receives as input an array of threshold
EcdsaSecretShare-s and locally computes the full private key (xpriv
). The main use case for this function is in an offline recovery setting where EcdsaSecretShare-s are collected manually and used to recover the full private key on an air-gapped server/device.
Returns a string containing the full xpriv
.
func (Ecdsa) Refresh
func (ecdsa Ecdsa) Refresh(roomUuid RoomUUID, share EcdsaSecretShare) (EcdsaSecretShare, EcdsaPublicKey, error)
Used for refreshing the secret material of all parties without altering the public key at all. Takes an EcdsaSecretShare as input and returns a new one for the same public key but with fresh key material. Be careful to delete the EcdsaSecretShare given as input before it is certain that all devices have properly stored the fresh EcdsaSecretShare that is output. Note that the new EcdsaSecretShare-s may only be used with each other, attempting to use older EcdsaSecretShare-s with newer ones for signing will result in failure.
The motivation for using refresh is to enhance security by switching the secret key material frequently, this means that an adversary will need to compromise multiple devices at the same time in order to compromise the private key.
Example
const N = 3
const API_KEY = "MY_API_KEY"
// An EcdsaSecretShare is generated using Keygen
// secretShare, pk, err := ecdsa.Keygen(...)
var secretShare EcdsaSecretShare
// Some time passes ...
// We now refresh the secret key material of our public key
ecdsa := NewEcdsa("")
refreshRoomUuid, err := ecdsa.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// Note: the public key returned here is the same as the one returned by the keygen
_, refreshedShare, err := ecdsa.Refresh(refreshRoomUuid, secretShare)
if err != nil {
panic(err)
}
// refreshedShare can now be used for signing under the same T threshold, as well as be refreshed again
_ = refreshedShare
func (Ecdsa) ReshareNewParty
func (ecdsa Ecdsa) ReshareNewParty(roomUuid RoomUUID, newThreshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (EcdsaSecretShare, EcdsaPublicKey, error)
WARNING: Key resharing is an advanced feature of the SDK. We strongly advise consulting with the Sodot team before using it, as incorrect usage might lead to the detriment of the private key security. To use the feature correctly, developers using this feature must make sure that at least n - t + 1
parties of the t-of-n
signing quorum delete their current shares before using the resharing of the private key. Also, after resharing, the resharing operation must not be considered complete until such deletion has occurred. Since deleting a share cannot be guaranteed cryptographically, it must be guaranteed by the software architecture (hence, by the developers using the SDK).
Resharing the private key of the t-of-n
quorum of signers, the resultant key shares will be of the exact same public key as the previous quorum. Resharing should be used in cases where we aim to modify the current t-of-n
quorum with a new quorum with newT-of-newN
signers for the same public key.
This is the method that a new party (meaning one that does not currently have an EcdsaSecretShare ) should use for receiving a key share in the new newT-of-newN
quorum. The method takes the same input parameters as Ecdsa.Keygen since for a new party joining the quorum the Reshare
operation is very similar to a Keygen
operation. In order to receive the KeygenID-s of parties that are already a part of the quorum, those parties will need to call [ExportID] and send the result to the parties in the new quorum.
Returns an EcdsaSecretShare with the secret data that can be used for signing as well as the EcdsaPublicKey.
Example
const N = 5
const newT = 5
const API_KEY = "MY_API_KEY"
// A signing quorum of `3-of-5` is set up without this party.
// Some time passes...
// Now this party wishes to be part of a new quorum of `5-of-6`.
//
// The app server creates a room for newN(= 6) parties.
ecdsa := NewEcdsa("")
reshareRoomUuid, err := ecdsa.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// At least T(= 3) parties now join the reshare room using the current secret key material (this will be done using `ReshareRemainingParty`), all new parties will then join the reshare room using their `KeygenPrivateKey` using `ReshareNewParty`.
keygenID, keygenPrivKey, err := ecdsa.InitKeygen()
if err != nil {
panic(err)
}
// This new party will send its `KeygenID` to all other parties.
// This party will also receive the `KeygenID`s of all other parties of the new quorum.
keygenIds := []KeygenID{"keygenID1", keygenID, "keygenID3", "keygenID4", "keygenID5", "keygenID6"} // Note that here we must include our own `KeygenID` as well, the order of the ids doesn't matter.
// Note: the public key returned here is the same as the one returned by the keygen
newShare, _, err := ecdsa.ReshareNewParty(reshareRoomUuid, newT, keygenPrivKey, keygenIds)
if err != nil {
panic(err)
}
// newShare can now be used for signing under the newT(= 5) threshold with the same public key, as well as be reshared again
_ = newShare
func (Ecdsa) ReshareRemainingParty
func (ecdsa Ecdsa) ReshareRemainingParty(roomUuid RoomUUID, newThreshold uint16, share EcdsaSecretShare, keygenIDs []KeygenID) (EcdsaSecretShare, EcdsaPublicKey, error)
WARNING: Key resharing is an advanced feature of the SDK. We strongly advise consulting with the Sodot team before using it, as incorrect usage might lead to the detriment of the private key security. To use the feature correctly, developers using this feature must make sure that at least n - t + 1
parties of the t-of-n
signing quorum delete their current shares before using the resharing of the private key. Also, after resharing, the resharing operation must not be considered complete until such deletion has occurred. Since deleting a share cannot be guaranteed cryptographically, it must be guaranteed by the software architecture (hence, by the developers using the SDK).
Resharing the private key of the t-of-n
quorum of signers, the resultant key shares will be of the exact same public key as the previous quorum. Resharing should be used in cases where we aim to modify the current t-of-n
quorum with a new quorum with newT-of-newN
signers for the same public key.
This is the method that a remaining party (meaning one that does currently have a key share) should use for receiving a new key share in the new newT-of-newN
quorum. The method takes the same input parameters as Ecdsa.ReshareNewParty except that it will use its existing EcdsaSecretShare instead of a new KeygenPrivateKey. In order to receive the KeygenID-s of parties that are already a part of the quorum, those parties will need to call [ExportID] and send the result to the parties in the new quorum.
Returns a new EcdsaSecretShare with the secret data that can be used for signing as well as the EcdsaPublicKey.
Example
const N = 5
const newT = 5
const API_KEY = "MY_API_KEY"
// A signing quorum of `3-of-5` is set up with this party.
// secretShare, pk, err := ecdsa.Keygen(...)
var secretShare EcdsaSecretShare
//
// Some time passes...
// Now this party wishes to be part of a new quorum of `5-of-6`.
//
// The app server creates a room for newN(= 6) parties.
ecdsa := NewEcdsa("")
reshareRoomUuid, err := ecdsa.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// At least T(= 3) parties now join the reshare room using the current secret key material (this will be done using `ReshareRemainingParty`), all new parties will then join the reshare room using their `KeygenPrivateKey` using `ReshareNewParty`.
keygenID, err := ecdsa.ExportID(secretShare) // This is a remaining party
if err != nil {
panic(err)
}
// This remaining party will send its `KeygenID` to all other parties (new - via an authenticated channel and remaining - via any channel).
// This party will also receive the `KeygenID`s of all other parties of the new quorum (new - via an authenticated channel and remaining - via any channel).
keygenIds := []KeygenID{"keygenID1", "keygenID2", keygenID, "keygenID4", "keygenID5", "keygenID6"} // Note that here we must include our own `KeygenID` as well, the order of the ids doesn't matter.
// Note: the public key returned here is the same as the one returned by the keygen
newShare, _, err := ecdsa.ReshareRemainingParty(reshareRoomUuid, newT, secretShare, keygenIds)
if err != nil {
panic(err)
}
// newShare can now be used for signing under the newT(= 5) threshold with the same public key, as well as be reshared again
_ = newShare
func (Ecdsa) Sign
func (ecdsa Ecdsa) Sign(roomUuid RoomUUID, share EcdsaSecretShare, msgHash MessageHash, derivationPath []uint32) (EcdsaSignature, error)
Sign a message with an EcdsaSecretShare created using Ecdsa.Keygen. The message must be hashed before signing, this can be done using the MessageHash type. derivationPath
must be a BIP-32 non-hardened derivation path to use for signing msgHash
.
Returns the signature of the message.
Example
// To sign a message, create a signing room on the server side, using your API_KEY
const T = 2
const API_KEY = "MY_API_KEY"
// An EcdsaSecretShare is generated using Keygen
// secretShare, pk, err := ecdsa.Keygen(...)
var secretShare EcdsaSecretShare
ecdsa := NewEcdsa("")
signingRoomUuid, err := ecdsa.CreateRoom(T, API_KEY)
if err != nil {
panic(err)
}
// Pick the derivation path of the public key you want to sign for
derivationPath := []uint32{44, 60, 0, 0, 0}
// Get the public key for the derivation path
derivedPubKey, err := ecdsa.DerivePubkey(secretShare, derivationPath)
if err != nil {
panic(err)
}
// The derived public key can now be used to verify signatures with the same derivation path
_ = derivedPubKey
// Hash the message
messageHash := MessageHashFromSha256([]byte("my message"))
// 2 parties join the signing room
signature, err := ecdsa.Sign(signingRoomUuid, secretShare, messageHash, derivationPath)
if err != nil {
panic(err)
}
// This signature can now be verified against pubkey
_ = signature
type EcdsaPublicKey
An ECDSA public key, generated by the Ecdsa.Keygen function. You can get the compressed form (33 bytes) via the EcdsaPublicKey.SerializeCompressed function, or the uncompressed form (65 bytes) via the EcdsaPublicKey.SerializeUncompressed function.
type EcdsaPublicKey [65]byte
func EcdsaPublicKeyFromString
func EcdsaPublicKeyFromString(s string) (EcdsaPublicKey, error)
func (*EcdsaPublicKey) Equal
func (pubkey *EcdsaPublicKey) Equal(other EcdsaPublicKey) bool
Equal returns true if the two public keys are equal.
func (*EcdsaPublicKey) SerializeCompressed
func (pubkey *EcdsaPublicKey) SerializeCompressed() (ret [33]byte)
SerializeCompressed returns the compressed public key as a [33]byte .
func (*EcdsaPublicKey) SerializeUncompressed
func (pubkey *EcdsaPublicKey) SerializeUncompressed() [65]byte
SerializeUncompressed returns the uncompressed public key as a [65]byte .
func (*EcdsaPublicKey) String
func (pubkey *EcdsaPublicKey) String() string
String returns the hex representation of the uncompressed public key.
type EcdsaSecretShare
The per-party private key material that is used for threshold signing
type EcdsaSecretShare string
func (EcdsaSecretShare) Bytes
func (share EcdsaSecretShare) Bytes() []byte
type EcdsaSignature
An ECDSA signature, generated by the Ecdsa.Sign function. You can get the DER encoded signature via the EcdsaSignature.DER method, or the r,s,v form via the EcdsaSignature.R, EcdsaSignature.S and EcdsaSignature.V methods.
type EcdsaSignature struct {
// contains filtered or unexported fields
}
func (EcdsaSignature) DER
func (sig EcdsaSignature) DER() []byte
R returns the r value for an r,s,v encoding of an ECDSA signature.
func (EcdsaSignature) Equal
func (sig EcdsaSignature) Equal(other EcdsaSignature) bool
func (EcdsaSignature) R
func (sig EcdsaSignature) R() []byte
R returns the r value for an r,s,v encoding of an ECDSA signature.
func (EcdsaSignature) S
func (sig EcdsaSignature) S() []byte
S returns the s value for an r,s,v encoding of an ECDSA signature.
func (EcdsaSignature) String
func (sig EcdsaSignature) String() string
String returns the hex representation of the compact signature representation (r || s)
func (EcdsaSignature) V
func (sig EcdsaSignature) V() byte
V returns the v value for an r,s,v encoding of an ECDSA signature.
type Ed25519
Class providing the functionality for the Ed25519 protocol via the FROST MPC protocol.
type Ed25519 struct {
HostUrl string
}
Example
const N = 3
const T = 2
const API_KEY = "MY_API_KEY"
ed25519 := NewEd25519("")
keygenRoomUuid, err := ed25519.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// All parties call initKeygen to get a KeygenID and a KeygenPrivateKey
keygenID, keygenPrivKey, err := ed25519.InitKeygen()
if err != nil {
panic(err)
}
// Send the keygenID to all other parties
_ = keygenID
// All parties receive the keygenIds from all other parties
keygenIds := []KeygenID{"keygenID1", "keygenID2"}
// All parties join the keygen room
secretShare, pk, err := ed25519.Keygen(keygenRoomUuid, N, T, keygenPrivKey, keygenIds)
if err != nil {
panic(err)
}
// The public key can now be used to verify signatures
_ = pk
// Pick the derivation path of the public key you want to sign for
derivationPath := []uint32{44, 60, 0, 0, 0}
// Get the public key for the derivation path
derivedPubKey, err := ed25519.DerivePubkey(secretShare, derivationPath)
if err != nil {
panic(err)
}
// The derived public key can now be used to verify signatures with the same derivation path
_ = derivedPubKey
// To sign a message, create a signing room on the server side, using your API_KEY
signingRoomUuid, err := ed25519.CreateRoom(T, API_KEY)
if err != nil {
panic(err)
}
// Define the message
message := []byte("my message")
// 2 parties join the signing room
signature, err := ed25519.Sign(signingRoomUuid, secretShare, message, derivationPath)
if err != nil {
panic(err)
}
// This signature can now be verified against pubkey
_ = signature
// Refreshing the secret key material
// Your server creates a room for 3 parties
refreshRoomUuid, err := ed25519.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// All parties join the refresh room
// Note: the public key returned here is the same as the one returned by the keygen
refreshedShare, _, err := ed25519.Refresh(refreshRoomUuid, secretShare)
if err != nil {
panic(err)
}
// Signing using the new secret key material
// The room is again created by the server
signingRoomUuid2, err := ed25519.CreateRoom(T, API_KEY)
if err != nil {
panic(err)
}
message2 := []byte("my new message")
signature2, err := ed25519.Sign(signingRoomUuid2, refreshedShare, message2, derivationPath)
if err != nil {
panic(err)
}
// This signature can now be verified against pubkey
_ = signature2
func NewEd25519
func NewEd25519(hostUrl string) Ed25519
NewEd25519 creates a new Ed25519 instance
host URL to use, if "" is passed, the default host url(SODOT_RELAY_URL
) will be used
func (Ed25519) CreateRoom
func (ed25519 Ed25519) CreateRoom(numParties uint16, apiKey string) (RoomUUID, error)
Creates a room for the given number of parties. A room is a one time instance used to perform a single MPC operation(keygen/signing/refresh etc.) between parties.
This function should be called in the backend so to not embed the API key in code that is distributed to the users. After the backend calls this function, the other parties can join the room by calling the relevant keygen/signing/refresh/etc. operation.
Returns a RoomUUID of the created room.
func (Ed25519) DerivePrivateKeyFromSpriv
func (ed25519 Ed25519) DerivePrivateKeyFromSpriv(spriv string, derivationPath []uint32) (PrivateKey, error)
Parses an spriv
string according to Sodot's non-hardened derivation, and returns the derived private key for a given BIP-32 non-hardened derivation path
func (Ed25519) DerivePubkey
func (ed25519 Ed25519) DerivePubkey(share Ed25519SecretShare, derivationPath []uint32) (Ed25519PublicKey, error)
Returns the derived public key for a Ed25519SecretShare for a given BIP-32 non-hardened derivation path.
func (Ed25519) DerivePubkeyFromSpub
func (ed25519 Ed25519) DerivePubkeyFromSpub(spub string, derivationPath []uint32) (Ed25519PublicKey, error)
DerivePubkeyFromXpub derives an Ecdsa public key from the given extended public key (Xpub) using the specified derivation path. Returns the derived public key or an error on failure.
func (Ed25519) ExportFullPrivateKey
func (ed25519 Ed25519) ExportFullPrivateKey(roomUuid RoomUUID, share Ed25519SecretShare, toExportID KeygenID) (string, error)
Combine all Ed25519SecretShare-s and export the full private key to a single party. Requires a threshold amount of parties to participate. toExportID
specifies the KeygenID of the party that should receive the private key, all parties must provide the same KeygenID or else export will fail.
The party being exported to will receive a string
containing the full spriv
, while the rest will receive an empty string.
func (Ed25519) ExportID
func (ed25519 Ed25519) ExportID(share Ed25519SecretShare) (KeygenID, error)
Extracts the KeygenID from an Ed25519SecretShare. Useful for reshare operations.
func (Ed25519) GetSpub
func (ed25519 Ed25519) GetSpub(share Ed25519SecretShare) (string, error)
GetSpub returns a base58 encoded extended public key (Spub) derived from a Ed25519SecretShare. Note that unlike ecdsa
or bip340
there's no standardization for the extended public key format for Ed25519. so S
stands for Sodot
, and is a somewhat custom derivation scheme adapted for Ed25519. The Spub can be used via Ed25519.DerivePubkeyFromSpub [BIP-32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format
func (Ed25519) ImportPrivateKeyImporter
func (ed25519 Ed25519) ImportPrivateKeyImporter(roomUuid RoomUUID, threshold uint16, privateKey PrivateKey, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID, optionalIsPrivateKeyRaw ...bool) (Ed25519SecretShare, Ed25519PublicKey, error)
WARNING: Private key import is an advanced feature of the SDK. We strongly advise consulting with the Sodot team before using it, due to a full private key being imported from a different system. Secret shares generated from imported private keys will always have the risk of the private key having been compromised in the past or in the future in case the private key is not deleted after the import operation.
Importing a full private key, and sharing into a T-of-N
sharing, the resultant key shares will be of the exact same public key as the full private key.
This is the method that an importing party (meaning one the party in possesion of the private key) should use for receiving a key share in the new T-of-N
quorum.
NOTE: optionalIsPrivateKeyRaw
is an optional parameter with a default value of false. It should be set to true if privateKey
is a raw private key and not an RFC 8032 encoded secret key. This will only be the case when manually creating this private key data, in case of importing from a standard Ed25519 system this won't be the case and can be left as the default false value.
Returns an Ed25519SecretShare with the secret data that can be used for signing as well as the Ed25519PublicKey.
Example
const N = 3
const T = 2
const API_KEY = "MY_API_KEY"
// A private key is created in some external system.
privateKey := PrivateKey("17b362b66ff82220f0d09f830c1aeaa52a42e85cae6d31c6c379d485103ba435")
// Some time passes...
// Now this party wishes to be part of a new quorum of `2-of-3` sharing of the private key.
//
// The app server creates a room for N(= 3) parties.
ed25519 := NewEd25519("")
importRoomUuid, err := ed25519.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// The other parties must join the import room using the `ImportPrivateKeyRecipient` method.
keygenID, keygenPrivKey, err := ed25519.InitKeygen()
if err != nil {
panic(err)
}
// This importing party will send its `KeygenID` to all other parties.
// This party will also receive the `KeygenID`s of all other parties of the new quorum.
keygenIds := []KeygenID{"keygenID1", keygenID, "keygenID3"} // Note that here we must include our own `KeygenID` as well, the order of the ids doesn't matter.
// Note: the public key returned here is the same as the public key of the imported key
newShare, _, err := ed25519.ImportPrivateKeyImporter(importRoomUuid, T, privateKey, keygenPrivKey, keygenIds)
if err != nil {
panic(err)
}
// newShare can now be used for signing under the T(= 2) threshold with the same public key
_ = newShare
func (Ed25519) ImportPrivateKeyRecipient
func (ed25519 Ed25519) ImportPrivateKeyRecipient(roomUuid RoomUUID, threshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (Ed25519SecretShare, Ed25519PublicKey, error)
WARNING: Private key import is an advanced feature of the SDK. We strongly advise consulting with the Sodot team before using it, due to a full private key being imported from a different system. Secret shares generated from imported private keys will always have the risk of the private key having been compromised in the past or in the future in case the private key is not deleted after the import operation.
Importing a full private key, and sharing into a T-of-N
sharing, the resultant key shares will be of the exact same public key as the full private key.
This is the method that a new party (meaning one that does not currently have the private key) should use for receiving a key share in the new T-of-N
quorum. The method takes the same input parameters as Ed25519.Keygen since for a new party joining the quorum the Import
operation is very similar to a Keygen
operation.
Returns an Ed25519SecretShare with the secret data that can be used for signing as well as the Ed25519PublicKey.
Example
const N = 3
const T = 2
const API_KEY = "MY_API_KEY"
// A private key is created in some external system.
// Some time passes...
// Now this party wishes to be part of a new quorum of `2-of-3` sharing of the private key.
//
// The app server creates a room for N(= 3) parties.
ed25519 := NewEd25519("")
importRoomUuid, err := ed25519.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// The party with the private key must join the import room using the `ImportPrivateKeyImporter` method.
keygenID, keygenPrivKey, err := ed25519.InitKeygen()
if err != nil {
panic(err)
}
// This new party will send its `KeygenID` to all other parties.
// This party will also receive the `KeygenID`s of all other parties of the new quorum.
keygenIds := []KeygenID{"keygenID1", keygenID, "keygenID3"} // Note that here we must include our own `KeygenID` as well, the order of the ids doesn't matter.
// Note: the public key returned here is the same as the public key of the imported key
newShare, _, err := ed25519.ImportPrivateKeyRecipient(importRoomUuid, T, keygenPrivKey, keygenIds)
if err != nil {
panic(err)
}
// newShare can now be used for signing under the T(= 2) threshold with the same public key
_ = newShare
func (Ed25519) InitKeygen
func (ed25519 Ed25519) InitKeygen() (KeygenID, KeygenPrivateKey, error)
All parties must call this function before calling Keygen. All parties receive a (KeygenID, KeygenPrivateKey) as an output from this function. The KeygenID must be sent through an authenticated communication channel to all other devices we wish to perform Ed25519.Keygen with. Once we have the KeygenID-s of all parties, then Ed25519.Keygen can be called with the same KeygenPrivateKey as was returned here.
func (Ed25519) Keygen
func (ed25519 Ed25519) Keygen(roomUuid RoomUUID, numParties uint16, threshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (Ed25519SecretShare, Ed25519PublicKey, error)
Generate a keypair for the given number of parties and threshold.
Requires configuring the threshold
and total number of participants (numParties
) who will hold a secret share. This party's KeygenPrivateKey must be provided as well as an array of KeygenID-s (created using Ed25519.InitKeygen) received from all other parties we wish to share the keypair with.
Returns an Ed25519PublicKey and an Ed25519SecretShare that contains the public key as well as the secret data that can be used for signing.
Example
// Your server side creates a room for 3 parties using its API_KEY
// Creating a room uuid should always happen on the server side using your API_KEY
const N = 3
const T = 2
const API_KEY = "MY_API_KEY"
ed25519 := NewEd25519("")
keygenRoomUuid, err := ed25519.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// All parties call initKeygen to get a KeygenID and a KeygenPrivateKey
keygenID, keygenPrivKey, err := ed25519.InitKeygen()
if err != nil {
panic(err)
}
// Send the keygenID to all other parties
_ = keygenID
// All parties receive the keygenIds from all other parties
keygenIds := []KeygenID{"keygenID1", "keygenID2"}
// All parties join the keygen room
secretShare, pk, err := ed25519.Keygen(keygenRoomUuid, N, T, keygenPrivKey, keygenIds)
if err != nil {
panic(err)
}
// The public key can now be used to verify signatures
_, _ = secretShare, pk
func (Ed25519) OfflineExportFullPrivateKey
func (ed25519 Ed25519) OfflineExportFullPrivateKey(shares []Ed25519SecretShare) (string, error)
Receives as input an array of threshold
Ed25519SecretShare-s and locally computes the full private key (spriv
). The main use case for this function is in an offline recovery setting where Ed25519SecretShare-s are collected manually and used to recover the full private key on an air-gapped server/device.
Returns a string containing the full spriv
.
func (Ed25519) Refresh
func (ed25519 Ed25519) Refresh(roomUuid RoomUUID, share Ed25519SecretShare) (Ed25519SecretShare, Ed25519PublicKey, error)
Used for refreshing the secret material of all parties without altering the public key at all. Takes an Ed25519SecretShare as input and returns a new one for the same public key but with fresh key material. Be careful to delete the Ed25519SecretShare given as input before it is certain that all devices have properly stored the fresh Ed25519SecretShare that is output. Note that the new Ed25519SecretShare-s may only be used with each other, attempting to use older Ed25519SecretShare-s with newer ones for signing will result in failure.
The motivation for using refresh is to enhance security by switching the secret key material frequently, this means that an adversary will need to compromise multiple devices at the same time in order to compromise the private key.
Example
const N = 3
const API_KEY = "MY_API_KEY"
// An Ed25519SecretShare is generated using Keygen
// secretShare, pk, err := ed25519.Keygen(...)
var secretShare Ed25519SecretShare
// Some time passes ...
// We now refresh the secret key material of our public key
ed25519 := NewEd25519("")
refreshRoomUuid, err := ed25519.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// Note: the public key returned here is the same as the one returned by the keygen
refreshedShare, _, err := ed25519.Refresh(refreshRoomUuid, secretShare)
if err != nil {
panic(err)
}
// refreshedShare can now be used for signing under the same T threshold, as well as be refreshed again
_ = refreshedShare
func (Ed25519) ReshareNewParty
func (ed25519 Ed25519) ReshareNewParty(roomUuid RoomUUID, newThreshold uint16, keygenPrivateKey KeygenPrivateKey, keygenIDs []KeygenID) (Ed25519SecretShare, Ed25519PublicKey, error)
WARNING: Key resharing is an advanced feature of the SDK. We strongly advise consulting with the Sodot team before using it, as incorrect usage might lead to the detriment of the private key security. To use the feature correctly, developers using this feature must make sure that at least n - t + 1
parties of the t-of-n
signing quorum delete their current shares before using the resharing of the private key. Also, after resharing, the resharing operation must not be considered complete until such deletion has occurred. Since deleting a share cannot be guaranteed cryptographically, it must be guaranteed by the software architecture (hence, by the developers using the SDK).
Resharing the private key of the t-of-n
quorum of signers, the resultant key shares will be of the exact same public key as the previous quorum. Resharing should be used in cases where we aim to modify the current t-of-n
quorum with a new quorum with newT-of-newN
signers for the same public key.
This is the method that a new party (meaning one that does not currently have an Ed25519SecretShare ) should use for receiving a key share in the new newT-of-newN
quorum. The method takes the same input parameters as Ed25519.Keygen since for a new party joining the quorum the Reshare
operation is very similar to a Keygen
operation. In order to receive the KeygenID-s of parties that are already a part of the quorum, those parties will need to call [ExportID] and send the result to the parties in the new quorum.
Returns an Ed25519SecretShare with the secret data that can be used for signing as well as the Ed25519PublicKey.
Example
const N = 5
const newT = 5
const API_KEY = "MY_API_KEY"
// A signing quorum of `3-of-5` is set up without this party.
// Some time passes...
// Now this party wishes to be part of a new quorum of `5-of-6`.
//
// The app server creates a room for newN(= 6) parties.
ed25519 := NewEd25519("")
reshareRoomUuid, err := ed25519.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// At least T(= 3) parties now join the reshare room using the current secret key material (this will be done using `ReshareRemainingParty`), all new parties will then join the reshare room using their `KeygenPrivateKey` using `ReshareNewParty`.
keygenID, keygenPrivKey, err := ed25519.InitKeygen()
if err != nil {
panic(err)
}
// This new party will send its `KeygenID` to all other parties.
// This party will also receive the `KeygenID`s of all other parties of the new quorum.
keygenIds := []KeygenID{"keygenID1", keygenID, "keygenID3", "keygenID4", "keygenID5", "keygenID6"} // Note that here we must include our own `KeygenID` as well, the order of the ids doesn't matter.
// Note: the public key returned here is the same as the one returned by the keygen
newShare, _, err := ed25519.ReshareNewParty(reshareRoomUuid, newT, keygenPrivKey, keygenIds)
if err != nil {
panic(err)
}
// newShare can now be used for signing under the newT(= 5) threshold with the same public key, as well as be reshared again
_ = newShare
func (Ed25519) ReshareRemainingParty
func (ed25519 Ed25519) ReshareRemainingParty(roomUuid RoomUUID, newThreshold uint16, share Ed25519SecretShare, keygenIDs []KeygenID) (Ed25519SecretShare, Ed25519PublicKey, error)
WARNING: Key resharing is an advanced feature of the SDK. We strongly advise consulting with the Sodot team before using it, as incorrect usage might lead to the detriment of the private key security. To use the feature correctly, developers using this feature must make sure that at least n - t + 1
parties of the t-of-n
signing quorum delete their current shares before using the resharing of the private key. Also, after resharing, the resharing operation must not be considered complete until such deletion has occurred. Since deleting a share cannot be guaranteed cryptographically, it must be guaranteed by the software architecture (hence, by the developers using the SDK).
Resharing the private key of the t-of-n
quorum of signers, the resultant key shares will be of the exact same public key as the previous quorum. Resharing should be used in cases where we aim to modify the current t-of-n
quorum with a new quorum with newT-of-newN
signers for the same public key.
This is the method that a remaining party (meaning one that does currently have a key share) should use for receiving a new key share in the new newT-of-newN
quorum. The method takes the same input parameters as Ed25519.ReshareNewParty except that it will use its existing Ed25519SecretShare instead of a new KeygenPrivateKey. In order to receive the KeygenID-s of parties that are already a part of the quorum, those parties will need to call [ExportID] and send the result to the parties in the new quorum.
Returns a new Ed25519SecretShare with the secret data that can be used for signing as well as the Ed25519PublicKey.
Example
const N = 5
const newT = 5
const API_KEY = "MY_API_KEY"
// A signing quorum of `3-of-5` is set up with this party.
// secretShare, pk, err := ed25519.Keygen(...)
var secretShare Ed25519SecretShare
//
// Some time passes...
// Now this party wishes to be part of a new quorum of `5-of-6`.
//
// The app server creates a room for newN(= 6) parties.
ed25519 := NewEd25519("")
reshareRoomUuid, err := ed25519.CreateRoom(N, API_KEY)
if err != nil {
panic(err)
}
// At least T(= 3) parties now join the reshare room using the current secret key material (this will be done using `ReshareRemainingParty`), all new parties will then join the reshare room using their `KeygenPrivateKey` using `ReshareNewParty`.
keygenID, err := ed25519.ExportID(secretShare) // This is a remaining party
if err != nil {
panic(err)
}
// This remaining party will send its `KeygenID` to all other parties (new - via an authenticated channel and remaining - via any channel).
// This party will also receive the `KeygenID`s of all other parties of the new quorum (new - via an authenticated channel and remaining - via any channel).
keygenIds := []KeygenID{"keygenID1", "keygenID2", keygenID, "keygenID4", "keygenID5", "keygenID6"} // Note that here we must include our own `KeygenID` as well, the order of the ids doesn't matter.
// Note: the public key returned here is the same as the one returned by the keygen
newShare, _, err := ed25519.ReshareRemainingParty(reshareRoomUuid, newT, secretShare, keygenIds)
if err != nil {
panic(err)
}
// newShare can now be used for signing under the newT(= 5) threshold with the same public key, as well as be reshared again
_ = newShare
func (Ed25519) Sign
func (ed25519 Ed25519) Sign(roomUuid RoomUUID, share Ed25519SecretShare, msg []byte, derivationPath []uint32) (Ed25519Signature, error)
Sign a message with an Ed25519SecretShare created using Ed25519.Keygen. derivationPath
must be a BIP-32 non-hardened derivation path to use for signing msg
.
Returns the signature of the message.
Example
// To sign a message, create a signing room on the server side, using your API_KEY
const T = 2
const API_KEY = "MY_API_KEY"
// An Ed25519SecretShare is generated using Keygen
// secretShare, pk, err := ed25519.Keygen(...)
var secretShare Ed25519SecretShare
ed25519 := NewEd25519("")
signingRoomUuid, err := ed25519.CreateRoom(T, API_KEY)
if err != nil {
panic(err)
}
// Pick the derivation path of the public key you want to sign for
derivationPath := []uint32{44, 60, 0, 0, 0}
// Get the public key for the derivation path
derivedPubKey, err := ed25519.DerivePubkey(secretShare, derivationPath)
if err != nil {
panic(err)
}
// The derived public key can now be used to verify signatures with the same derivation path
_ = derivedPubKey
// Define the message
message := []byte("my message")
// 2 parties join the signing room
signature, err := ed25519.Sign(signingRoomUuid, secretShare, message, derivationPath)
if err != nil {
panic(err)
}
// This signature can now be verified against pubkey
_ = signature
type Ed25519PublicKey
An Ed25519 public key, generated by the Ed25519.Keygen function.
type Ed25519PublicKey [32]byte
func Ed25519PublicKeyFromString
func Ed25519PublicKeyFromString(s string) (Ed25519PublicKey, error)
Ed25519PublicKeyFromString parses a hex string into an Ed25519PublicKey.
func (*Ed25519PublicKey) String
func (pubkey *Ed25519PublicKey) String() string
String returns the hex representation of the public key.
type Ed25519SecretShare
The per-party private key material that is used for threshold signing
type Ed25519SecretShare string
func (Ed25519SecretShare) Bytes
func (share Ed25519SecretShare) Bytes() []byte
Bytes returns the raw representation of the Ed25519SecretShare.
type Ed25519Signature
An Ed25519 signature, generated by the Ed25519.Sign function.
type Ed25519Signature [64]byte
func (*Ed25519Signature) String
func (sig *Ed25519Signature) String() string
String returns the hex representation of the signature.
type KeygenID
KeygenID is a base58 string representing the ID of the party in the key generation protocol.
type KeygenID string
type KeygenPrivateKey
KeygenPrivateKey is a hex string representing the private key of the corresponding KeygenID.
type KeygenPrivateKey string
func (KeygenPrivateKey) Bytes
func (privkey KeygenPrivateKey) Bytes() []byte
Bytes returns the raw representation of the KeygenPrivateKey.
type MessageHash
A 32 byte message hash used for signing ECDSA messages.
// A SHA256 hash of the string 'Hello World'
messageHash := MessageHashFromSha256([]byte('Hello World'))
// A Keccak256 hash of the string 'Hello World'
messageHash := MessageHashFromKeccak256([]byte('Hello World'))
// In case you already have a hashed value you can just call the constructor with it
messageHash := MessageHash{71, 111, 38, ... , 23}
type MessageHash [32]byte
func MessageHashFromKeccak256
func MessageHashFromKeccak256(data []byte) MessageHash
Creates a MessageHash by hashing data
with Keccak256
func MessageHashFromSha256
func MessageHashFromSha256(data []byte) MessageHash
Creates a MessageHash by hashing data
with SHA256
func MessageHashFromSha256d
func MessageHashFromSha256d(data []byte) MessageHash
Creates a MessageHash by hashing data
with Double SHA256 (Bitcoin's Sha256d)
type PrivateKey
PrivateKey is a 64 characters hex string representing some private key.
type PrivateKey string
func (PrivateKey) Bytes
func (privkey PrivateKey) Bytes() []byte
Bytes returns the raw representation of the PrivateKey.
type RoomUUID
RoomUUID is a hex string representing a room, which is created for each MPC operation (e.g. Signing, Keygen, Refresh, etc.) and acts as a session identifier to synchronize all involved parties.
type RoomUUID string
func (RoomUUID) Bytes
func (uuid RoomUUID) Bytes() []byte
Bytes returns the raw representation of the RoomUUID.