Description
Spinoff of #291.
Invoices are an extension of our existing address format, that include an expiry time. Using invoices requires more interaction with a hashmail courier service than addresses, but they allow for receiving a balance of a grouped asset.
From an invoice, we can compute a unique ID. This commits to most invoice fields, but excludes asset.AssetCommitmentKey()
so that any member of an asset group could satisfy an invoice.
invoiceID := H(tapscriptRoot || asset.TapCommitmentKey() || chain_params || asset_version
|| internal_key || script_key || amount || courier_addr)
To send a grouped asset, the sender needs to communicates a LeafMap
to the receiver. This provides the missing information to compute the on-chain output for the transfer.
outputLeafMap := map[asset.ID]uint64
With an invoice, we can compute a unique ID. The sender can use the ID + information from the asset transfer inputs to upload a leaf map to a hashmail courier. The receiver polls the hashmail courier, and once they fetch the leaf map they can reconstruct the on-chain output for the send.
Specifically, on the sender side:
firstScriptKey := transfer.inputs[0].ScriptKey
invoiceID := invoice.ID()
invoiceSharedSecret := ECDH(firstScriptKey, invoice.ScriptKey)
// upload sender pubkey so the receiver can derive invoiceSharedSecret
courier.WriteSenderKey(invoiceID, firstScriptKey)
// build the output leaf map from the set of all transfer outputs
outputLeafMap := make(map[asset.ID]uint64, len(transfer.inputs))
for _, vOutput := range transfer.outputs {
// filter out change outputs, asset splits, etc.
if invoice.Satisfies(vOutput.Asset) {
outputLeafMap[vOutput.Asset.ID] = vOutput.Asset.Amount
}
}
// upload the leaf map the receiver needs to find the on-chain TX
courier.WriteLeafMap(invoiceSharedSecret, outputLeafMap)
For the receiver:
newBlockChan := cfg.ChainBridge.RegisterBlockEpochNtfn()
for {
select {
// On each new block, check for new data at the hashmail courier
// for all unexpired invoices. If we received some data, complete
// the receive process.
case newBlock := <- newBlockChan:
invoices := store.LoadInvoices()
invoices := invoices.RemoveExpired()
for _, invoice := range invoices {
senderKey := courier.ReadSenderKey(invoiceID)
if senderKey != nil {
c.receivePayment(invoice, senderKey)
}
}
}
}
func (c *Custodian) receivePayment(invoice pendingInvoice, senderKey asset.SerializedKey) {
// Pull the output leaf map from the hashmail courier
invoiceSharedSecret := ECDH(senderKey, invoice.ScriptKey)
outputLeafMap := c.cfg.courier.ReadLeafMap(invoiceSharedSecret)
// comput the on-chain output that pays the invoice
onChainOutput := computeOutput(invoice, outputLeafMap)
// register event for a TX with the above output, continue custodian as usual
}
Metadata
Metadata
Assignees
Labels
Type
Projects
Status