Skip to content

Commit

Permalink
[Multisig] Implement new camino-node multisig logic
Browse files Browse the repository at this point in the history
  • Loading branch information
peak3d committed Mar 19, 2023
1 parent dc805fc commit ec5b6e8
Show file tree
Hide file tree
Showing 25 changed files with 826 additions and 503 deletions.
8 changes: 5 additions & 3 deletions e2e_tests/camino/pchain_nomock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ describe("Camino-PChain-Add-Validator", (): void => {
(async function () {
const stakeAmount: any = await pChain.getMinStake()
const subnetAuthCredentials: [number, Buffer][] = [[0, pAddresses[1]]]
const nodeCredentials: [number, Buffer] = [2, pAddresses[2]]
const unsignedTx: UnsignedTx = await pChain.buildAddSubnetValidatorTx(
undefined,
[P(addrB)],
Expand All @@ -249,8 +248,11 @@ describe("Camino-PChain-Add-Validator", (): void => {
createdSubnetID.value,
memo,
new BN(0),
subnetAuthCredentials,
nodeCredentials
{
addresses: [pAddresses[1]],
threshold: 1,
signer: subnetAuthCredentials
}
)

const tx: Tx = unsignedTx.sign(pKeychain)
Expand Down
6 changes: 5 additions & 1 deletion examples/platformvm/buildAddSubnetValidatorTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ const main = async (): Promise<any> => {
subnetID,
memo,
asOf,
subnetAuthCredentials
{
addresses: [pAddresses[3], pAddresses[1]],
threshold: 2,
signer: subnetAuthCredentials
}
)
const tx: Tx = unsignedTx.sign(pKeychain)
const txid: string = await pchain.issueTx(tx)
Expand Down
6 changes: 5 additions & 1 deletion examples/platformvm/buildCreateChainTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ const main = async (): Promise<any> => {
genesisData,
memo,
asOf,
subnetAuthCredentials
{
addresses: [pAddresses[3], pAddresses[1]],
threshold: 2,
signer: subnetAuthCredentials
}
)

const tx: Tx = unsignedTx.sign(pKeychain)
Expand Down
80 changes: 52 additions & 28 deletions examples/platformvm/buildExportTx-XChain-Msig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import {
PlatformVMAPI,
KeyChain,
UTXOSet,
UnsignedTx,
Tx
PlatformVMConstants
} from "caminojs/apis/platformvm"
import {
MultisigAliasSet,
MultisigKeyChain,
MultisigKeyPair,
OutputOwners
Expand Down Expand Up @@ -41,8 +39,8 @@ const memo: Buffer = Buffer.from(
)
const asOf: BN = new BN(0)
const msigAliasArray = [
"P-kopernikus1fq0jc8svlyazhygkj0s36qnl6s0km0h3uuc99w",
"P-kopernikus1k4przmfu79ypp4u7y98glmdpzwk0u3sc7saazy"
"P-kopernikus1z5tv4tg04kf4l9ghclw6ssek8zugs7yd65prpl"
//"P-kopernikus1t5qgr9hcmf2vxj7k0hz77kawf9yr389cxte5j0"
]

let pchain: PlatformVMAPI
Expand Down Expand Up @@ -80,27 +78,63 @@ const main = async (): Promise<any> => {
for (const msigAlias of msigAliasArray) {
const platformVMUTXOResponse: any = await pchain.getUTXOs([msigAlias])
const utxoSet: UTXOSet = platformVMUTXOResponse.utxos
const unsignedTx: UnsignedTx = await pchain.buildExportTx(

var unsignedTx = await pchain.buildExportTx(
utxoSet,
new BN(1000000000),
xChainBlockchainID,
xAddressStrings,
[[msigAlias], pAddressStrings],
[msigAlias],
memo,
asOf,
locktime,
threshold
)

// UnsignedTx now contains additionally to the sigIdx all OutputOwners
// which must be fulfilled. This example makes 2 transactions to show
// the workflow of both variants.

// Variant 1 -> TX can be fired directly
var tx = unsignedTx.sign(pKeychain)
var txid = await pchain.issueTx(tx)
console.log(`Success! TXID: ${txid}`)

await new Promise((resolve) => setTimeout(resolve, 1000))

// Variant 2 -> Create a multisig keychain and use CaminoCredentials

// We need to fetch UTXOs again because the previous are not longer valid
unsignedTx = await pchain.buildExportTx(
utxoSet,
new BN(1000000000),
xChainBlockchainID,
xAddressStrings,
[[msigAlias], pAddressStrings],
[msigAlias],
memo,
asOf,
locktime,
threshold
)

// Create MultiSig resolver which resolves all aliases
// If you provide addresses, no wildcard sigIndices are created
// Create the hash from the tx
const txbuff = unsignedTx.toBuffer()
const msg: Buffer = Buffer.from(
createHash("sha256").update(txbuff).digest()
)

// MultiSigAliasSet
// Create the Multisig keychain
const msigAliasBuffer = pchain.parseAddress(msigAlias)
const owner = await pchain.getMultisigAlias(msigAlias)

var msSet = new MultisigAliasSet(
const msKeyChain = new MultisigKeyChain(
avalanche.getHRP(),
PChainAlias,
msg,
PlatformVMConstants.SECPMULTISIGCREDENTIAL,
unsignedTx.getTransaction().getOutputOwners(),
new Map([
[
msigAliasBuffer.toString("hex"),
Expand All @@ -110,22 +144,7 @@ const main = async (): Promise<any> => {
owner.threshold
)
]
]),
new Set(pAddresses.map((a) => a.toString("hex")))
)
// Note: inplace modifying of input indices
unsignedTx.getTransaction().resolveMultisigIndices(msSet)

// Create the hash from the tx
const txbuff = unsignedTx.toBuffer()
const msg: Buffer = Buffer.from(
createHash("sha256").update(txbuff).digest()
)

const msKeyChain = new MultisigKeyChain(
msg,
avalanche.getHRP(),
PChainAlias
])
)

for (const address of pAddresses) {
Expand All @@ -136,10 +155,15 @@ const main = async (): Promise<any> => {
msKeyChain.addKey(new MultisigKeyPair(msKeyChain, address, signature))
}

// Create signature indices (throws if not able to do so)
msKeyChain.buildSignatureIndices()

// Sign the transaction with msig keychain and issue
const tx: Tx = unsignedTx.sign(msKeyChain)
const txid: string = await pchain.issueTx(tx)
tx = unsignedTx.sign(msKeyChain)
txid = await pchain.issueTx(tx)
console.log(`Success! TXID: ${txid}`)

await new Promise((resolve) => setTimeout(resolve, 1000))
}
} catch (e: any) {
console.log(e)
Expand Down
43 changes: 13 additions & 30 deletions src/apis/platformvm/addsubnetvalidatortx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import BinTools from "../../utils/bintools"
import { PlatformVMConstants } from "./constants"
import { TransferableOutput } from "./outputs"
import { TransferableInput } from "./inputs"
import { Credential, MultisigAliasSet, SigIdx, Signature } from "../../common"
import { Credential, OutputOwners, SigIdx, Signature } from "../../common"
import { BaseTx } from "./basetx"
import { DefaultNetworkID } from "../../utils/constants"
import { Serialization, SerializedEncoding } from "../../utils/serialization"
Expand Down Expand Up @@ -60,8 +60,8 @@ export class AddSubnetValidatorTx extends BaseTx {
protected subnetID: Buffer = Buffer.alloc(32)
protected subnetAuth: SubnetAuth
protected sigCount: Buffer = Buffer.alloc(4)
protected nodeSigIdx: SigIdx = undefined
protected sigIdxs: SigIdx[] = [] // idxs of subnet auth signers
protected withNodeSig: boolean = false

/**
* Returns the id of the [[AddSubnetValidatorTx]]
Expand Down Expand Up @@ -210,12 +210,8 @@ export class AddSubnetValidatorTx extends BaseTx {
this.sigCount.writeUInt32BE(this.sigIdxs.length, 0)
}

setNodeSignatureIdx(addressIdx: number, address: Buffer): void {
this.nodeSigIdx = new SigIdx()
const b: Buffer = Buffer.alloc(4)
b.writeUInt32BE(addressIdx, 0)
this.nodeSigIdx.fromBuffer(b)
this.nodeSigIdx.setSource(address)
includeNodeSignature(): void {
this.withNodeSig = true
}

/**
Expand All @@ -234,9 +230,6 @@ export class AddSubnetValidatorTx extends BaseTx {
this.subnetAuth.setAddressIndices(sigIdxs.map((idx) => idx.toBuffer()))
}

getNodeSigIdx(): SigIdx {
return this.nodeSigIdx
}
getCredentialID(): number {
return PlatformVMConstants.SECPCREDENTIAL
}
Expand All @@ -252,7 +245,7 @@ export class AddSubnetValidatorTx extends BaseTx {
sign(msg: Buffer, kc: KeyChain): Credential[] {
const creds: Credential[] = super.sign(msg, kc)
const sigidxs: SigIdx[] = this.getSigIdxs()
const cred: Credential = SelectCredentialClass(this.getCredentialID())
let cred: Credential = SelectCredentialClass(this.getCredentialID())
for (let i: number = 0; i < sigidxs.length; i++) {
const keypair: KeyPair = kc.getKey(sigidxs[`${i}`].getSource())
const signval: Buffer = keypair.sign(msg)
Expand All @@ -262,28 +255,18 @@ export class AddSubnetValidatorTx extends BaseTx {
}
creds.push(cred)

if (this.getNodeSigIdx() != undefined) {
// sign with node sig
this.signWithNodeSig(kc, msg, creds)
if (this.withNodeSig) {
cred = cred.create()
const keypair: KeyPair = kc.getKey(this.nodeID)
const signval: Buffer = keypair.sign(msg)
const sig: Signature = new Signature()
sig.fromBuffer(signval)
cred.addSignature(sig)
creds.push(cred)
}
return creds
}

resolveMultisigIndices(resolver: MultisigAliasSet) {
super.resolveMultisigIndices(resolver)
this.setSigIdxs(resolver.resolveMultisig(this.sigIdxs))
}

private signWithNodeSig(kc: KeyChain, msg: Buffer, creds: Credential[]) {
const cred: Credential = SelectCredentialClass(this.getCredentialID())
const keypair: KeyPair = kc.getKey(this.getNodeSigIdx().getSource())
const signval: Buffer = keypair.sign(msg)
const sig: Signature = new Signature()
sig.fromBuffer(signval)
cred.addSignature(sig)
creds.push(cred)
}

/**
* Class representing an unsigned AddSubnetValidator transaction.
*
Expand Down
Loading

0 comments on commit ec5b6e8

Please sign in to comment.