Skip to content
This repository has been archived by the owner on May 11, 2021. It is now read-only.

Signatory set transitions #8

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/deposit.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import * as bitcoin from 'bitcoinjs-lib'

import { createOutput } from './reserve'
import { BitcoinNetwork, SignatoryMap, ValidatorMap } from './types'
import { BitcoinNetwork, SignatorySet } from './types'

export function createBitcoinTx(
validators: ValidatorMap,
signatoryKeys: SignatoryMap,
signatorySet: SignatorySet,
utxos: any,
destAddress: Buffer,
network: BitcoinNetwork
): bitcoin.Transaction {
let { validators, signatoryKeys } = signatorySet
let tx = new bitcoin.Transaction()

// add the utxos as inputs
Expand Down
75 changes: 44 additions & 31 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
getSignatorySet,
buildOutgoingTx,
createOutput,
getVotingPowerThreshold
getVotingPowerThreshold,
getP2ssInfo
} from './reserve'

import {
Expand All @@ -29,6 +30,7 @@ import {
isSignatoryCommitmentTx,
isSignatureTx,
BitcoinPegSignatureTx,
SignatorySet,
BitcoinPegDepositTx
} from './types'
// TODO: get this from somewhere else
Expand Down Expand Up @@ -60,27 +62,15 @@ let bitcoinPeg: any = function(
// bitcoin blockchain headers (so we can SPV-verify txs)
state.chain = [initialHeader]

// commitments by validators to their secp256k1 pubkeys, which we can use
// on the bitcoin blockchain
state.signatoryKeys = {}

// index of transactions we have already processed, to prevent replaying
// relayed txs
state.processedTxs = {}

// all UTXOs that the signatory set can spend (from deposit transactions
// and our own change outputs)
state.utxos = []

// queue of withdrawals to be processed in the next disbursal
state.withdrawals = []

// transaction being signed by the signatories
state.signingTx = null

// most recent tx completely signed by the signatories which can be relayed
state.signedTx = null
state.prevSignedTx = null
// map of p2ss address -> signatory set data
state.signatorySets = {}
}

function txHandler(
Expand Down Expand Up @@ -173,7 +163,7 @@ let bitcoinPeg: any = function(
// TODO: compare against older validator sets
let expectedP2ss = createOutput(
context.validators,
state.signatoryKeys,
state.signatorySets[state.currentP2ssAddress].signatoryKeys,
network
)
let depositOutput: any = bitcoinTx.outs[0]
Expand Down Expand Up @@ -205,6 +195,7 @@ let bitcoinPeg: any = function(
address: coins.hashToAddress(addressHash),
amount: depositOutput.value
})
console.log(depositOutput)
console.log(
'minting ' +
depositOutput.value +
Expand All @@ -213,7 +204,7 @@ let bitcoinPeg: any = function(
)

// add deposit outpoint to reserve wallet
state.utxos.push({
state.signatorySets[state.currentP2ssAddress].utxos.push({
txid,
index: 0,
amount: depositOutput.value
Expand Down Expand Up @@ -257,8 +248,25 @@ let bitcoinPeg: any = function(
throw Error('Invalid signature')
}

// add signatory key to state
state.signatoryKeys[validatorKeyBase64] = signatoryKey
let previousSignatorySetKeys = state.currentP2ssAddress
? state.signatorySets[state.currentP2ssAddress].signatoryKeys
: {}
// construct new signatory set
let newSignatorySet: SignatorySet = {
utxos: [],
validators: context.validators,
prevSignedTx: null,
signedTx: null,
signingTx: null,
signatoryKeys: {
...previousSignatorySetKeys,
[validatorKeyBase64]: signatoryKey
}
}

let p2ssInfo = getP2ssInfo(newSignatorySet, network)
state.signatorySets[p2ssInfo.address] = newSignatorySet
state.currentP2ssAddress = p2ssInfo.address
}

// signature tx, add signatory's sig to outgoing transaction
Expand All @@ -268,7 +276,8 @@ let bitcoinPeg: any = function(
context: BitcoinPegContext
) {
let { signatoryIndex, signatures } = tx
let { signingTx, signatoryKeys } = state
let currentSignatorySet = state.signatorySets[state.currentP2ssAddress]
let { signingTx } = currentSignatorySet

if (signingTx == null) {
throw Error('No pending outgoing transaction')
Expand Down Expand Up @@ -297,17 +306,20 @@ let bitcoinPeg: any = function(
if (signatory == null) {
throw Error('Invalid signatory index')
}
let signatoryKey = signatoryKeys[signatory.validatorKey]
let signatoryKey = currentSignatorySet.signatoryKeys[signatory.validatorKey]

// compute hashes that should have been signed
let bitcoinTx = buildOutgoingTx(
signingTx,
context.validators,
signatoryKeys,
currentSignatorySet.signatoryKeys,
network
)
// TODO: handle dynamic signatory sets
let p2ss = createWitnessScript(context.validators, signatoryKeys)
let p2ss = createWitnessScript(
context.validators,
currentSignatorySet.signatoryKeys
)
let sigHashes = signingTx.inputs.map((input, i) =>
bitcoinTx.hashForWitnessV0(
i,
Expand All @@ -333,15 +345,15 @@ let bitcoinPeg: any = function(
let votingPowerThreshold = getVotingPowerThreshold(signatorySet)
if (signingTx.signedVotingPower >= votingPowerThreshold) {
// done signing, now the tx is valid and can be relayed
state.prevSignedTx = state.signedTx
state.signedTx = signingTx
state.signingTx = null
currentSignatorySet.prevSignedTx = currentSignatorySet.signedTx
currentSignatorySet.signedTx = signingTx
currentSignatorySet.signingTx = null

// add change output to our UTXOs
let txHash = bitcoinTx.getHash()
let changeIndex = bitcoinTx.outs.length - 1
let changeOutput: any = bitcoinTx.outs[changeIndex]
state.utxos.push({
currentSignatorySet.utxos.push({
txid: txHash,
index: changeIndex,
amount: changeOutput.value
Expand All @@ -355,16 +367,17 @@ let bitcoinPeg: any = function(
// TODO: in the future we won't disburse every time there is a withdrawal,
// we will wait until we're ready to make a checkpoint (e.g. a few
// times a day or when the signatory set has changed)
let currentSignatorySet = state.signatorySets[state.currentP2ssAddress]
if (state.withdrawals.length === 0) return
if (state.signingTx != null) return
if (currentSignatorySet.signingTx != null) return

let inputs = state.utxos
state.utxos = []
let inputs = currentSignatorySet.utxos
currentSignatorySet.utxos = []

let outputs = state.withdrawals
state.withdrawals = []

state.signingTx = {
currentSignatorySet.signingTx = {
inputs,
outputs,
signatures: [],
Expand Down
15 changes: 9 additions & 6 deletions src/relay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from './signatory'
import {
BitcoinNetwork,
SignatoryMap,
SignatoryKeyMap,
SignedTx,
ValidatorMap,
Header,
Expand Down Expand Up @@ -104,8 +104,7 @@ export class Relay {
async step() {
let rpc = this.bitcoinRPC
let lc = this.lotionLightClient
let p2ss = await getSignatoryScriptHashFromPegZone(lc)
let p2ssAddress = await getCurrentP2ssAddress(lc, this.network)
let p2ssAddress: string = await lc.state.bitcoin.currentP2ssAddress
await rpc.importAddress(
/*address=*/ p2ssAddress,
/*label=*/ '',
Expand Down Expand Up @@ -165,10 +164,14 @@ export class Relay {
}

// Now check for a completed transaction on the peg zone.
let signedTx: SignedTx | null = await lc.state.bitcoin.signedTx
let signedTx: SignedTx | null = await lc.state.bitcoin.signatorySets[
p2ssAddress
].signedTx
if (signedTx) {
let validators = convertValidatorsToLotion(lc.validators)
let signatoryKeys: SignatoryMap = await lc.state.bitcoin.signatoryKeys
let signatoryKeys: SignatoryKeyMap = await lc.state.bitcoin.signatorySets[
p2ssAddress
].signatoryKeys
let finalizedTx = buildDisbursalTransaction(
signedTx,
validators,
Expand Down Expand Up @@ -212,7 +215,7 @@ export function convertValidatorsToLotion(
function buildDisbursalTransaction(
signedTx: SignedTx,
validators: ValidatorMap,
signatoryKeys: SignatoryMap,
signatoryKeys: SignatoryKeyMap,
network: BitcoinNetwork
) {
// build tx
Expand Down
41 changes: 33 additions & 8 deletions src/reserve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import {
TxOutput
} from 'bitcoinjs-lib'

import { BitcoinNetwork, SignatoryMap, SigningTx, ValidatorMap } from './types'
import {
BitcoinNetwork,
SignatoryKeyMap,
SigningTx,
SignatorySet,
ValidatorMap
} from './types'

const MAX_SIGNATORIES = 76
const MIN_RELAY_FEE = 1000
Expand Down Expand Up @@ -64,19 +70,20 @@ export function getVotingPowerThreshold(

export function createWitnessScript(
validators: ValidatorMap,
signatoryKeys: SignatoryMap
signatoryKeys: SignatoryKeyMap
) {
// get signatory key for each signatory
let signatories: { pubkey: string; votingPower: number }[] = getSignatorySet(
validators
)
.filter(validator => {
return signatoryKeys[validator.validatorKey]
})
.map(({ validatorKey, votingPower }) => {
let pubkeyHex
let pubkeyBytes: Buffer = signatoryKeys[validatorKey]
pubkeyHex = pubkeyBytes.toString('hex')
let pubkeyHex = pubkeyBytes.toString('hex')
return { pubkey: pubkeyHex, votingPower }
})
.filter(s => s.pubkey != null)

let twoThirdsVotingPower = getVotingPowerThreshold(signatories)

Expand Down Expand Up @@ -130,11 +137,10 @@ export function getSignatorySet(validators: ValidatorMap) {
export function buildOutgoingTx(
signingTx: SigningTx,
validators: ValidatorMap,
signatoryKeys: SignatoryMap,
signatoryKeys: SignatoryKeyMap,
network: BitcoinNetwork
) {
let { inputs, outputs } = signingTx
console.log(inputs)
let tx = new Transaction()
let totalAmount = 0

Expand Down Expand Up @@ -174,9 +180,28 @@ export function buildOutgoingTx(
return tx
}

/**
* Get the pay-to-signatory-set hex script and address.
*
*/
export function getP2ssInfo(
signatorySet: SignatorySet,
network: BitcoinNetwork
) {
let p2ss = createWitnessScript(
signatorySet.validators,
signatorySet.signatoryKeys
)
let p2ssAddress = payments.p2wsh({
redeem: { output: p2ss },
network: networks[network === 'mainnet' ? 'bitcoin' : network]
}).address as string
return { address: p2ssAddress, script: p2ss }
}

export function createOutput(
validators: ValidatorMap,
signatoryKeys: SignatoryMap,
signatoryKeys: SignatoryKeyMap,
network: BitcoinNetwork
) {
// p2ss = pay to signatory set
Expand Down
19 changes: 12 additions & 7 deletions src/signatory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
ValidatorMap,
BitcoinNetwork,
LightClient,
SigningTx
SigningTx,
SignatorySet
} from './types'
import {
getSignatorySet,
Expand Down Expand Up @@ -58,12 +59,16 @@ export async function signDisbursal(
network: BitcoinNetwork
) {
let signatoryPub = secp.publicKeyCreate(signatoryPriv)
let validators = convertValidatorsToLotion(client.validators)
let signatoryKeys = await client.state.bitcoin.signatoryKeys
let signatorySet = getSignatorySet(validators)
// let validators = convertValidatorsToLotion(client.validators)
let p2ssAddress = await client.state.bitcoin.currentP2ssAddress
let signatorySet: SignatorySet = await client.state.bitcoin.signatorySets[
p2ssAddress
]
let { signatoryKeys, validators } = signatorySet
let signatoryKeyAndPower = getSignatorySet(validators)
let signatoryIndex
for (let i = 0; i < signatorySet.length; i++) {
let signatory = signatorySet[i]
for (let i = 0; i < signatoryKeyAndPower.length; i++) {
let signatory = signatoryKeyAndPower[i]
if (signatoryKeys[signatory.validatorKey].equals(signatoryPub)) {
// found our signatory
signatoryIndex = i
Expand All @@ -74,7 +79,7 @@ export async function signDisbursal(
throw Error('Given signatory key not found in signatory set')
}

let signingTx: SigningTx = await client.state.bitcoin.signingTx
let { signingTx } = signatorySet
if (signingTx == null) {
throw Error('No tx to be signed')
}
Expand Down
Loading