Skip to content

Commit

Permalink
[PVM] MultisigAlias and MultisigAliasTx implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
charalarg committed May 30, 2023
1 parent 1e93d27 commit 74a8236
Show file tree
Hide file tree
Showing 11 changed files with 872 additions and 4 deletions.
2 changes: 1 addition & 1 deletion examples/common/examplesConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"host": "localhost",
"port": 9650,
"protocol": "http",
"networkID": 12345
"networkID": 1002
}
6 changes: 6 additions & 0 deletions examples/common/examplesKopernikusConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"host": "localhost",
"port": 9650,
"protocol": "http",
"networkID": 12345
}
221 changes: 221 additions & 0 deletions examples/platformvm/buildMultisigAliasTx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import { Avalanche, BinTools, BN, Buffer } from "caminojs/index"
import {
PlatformVMAPI,
UTXOSet,
UnsignedTx,
Tx,
PlatformVMConstants,
MultisigAliasParams
} from "caminojs/apis/platformvm"
import {
MultisigKeyChain,
MultisigKeyPair,
OutputOwners
} from "caminojs/common"

import {
PrivateKeyPrefix,
DefaultLocalGenesisPrivateKey,
DefaultLocalGenesisPrivateKey2,
PChainAlias
} from "caminojs/utils"
import { ExamplesConfig } from "../common/examplesConfig"
import createHash from "create-hash"

const config: ExamplesConfig = require("../common/examplesKopernikusConfig.json")
const bintools = BinTools.getInstance()
const avalanche: Avalanche = new Avalanche(
config.host,
config.port,
config.protocol,
config.networkID
)

const privKey1: string = `${PrivateKeyPrefix}${DefaultLocalGenesisPrivateKey}`
const privKey2: string = `${PrivateKeyPrefix}${DefaultLocalGenesisPrivateKey2}`
const asOf: BN = new BN(0)
const msigAlias = "P-kopernikus1t5qgr9hcmf2vxj7k0hz77kawf9yr389cxte5j0"

let pchain: PlatformVMAPI

const InitAvalanche = async () => {
await avalanche.fetchNetworkSettings()
pchain = avalanche.PChain()
}

const sendMultisigAliasTxCreate = async (): Promise<any> => {
const pKeychain = pchain.keyChain()
pKeychain.importKey(privKey1)
const pAddressStrings = pchain.keyChain().getAddressStrings()

{
const newMemo = "100"

const multisigAliasParams: MultisigAliasParams = {
memo: newMemo,
owners: new OutputOwners(
[pchain.parseAddress(pAddressStrings[0])],
new BN(0),
1
),
auth: []
}

const unsignedTx: UnsignedTx = await pchain.buildMultisigAliasTx(
undefined,
pAddressStrings,
pAddressStrings,
multisigAliasParams,
undefined,
asOf,
1
)

const tx: Tx = unsignedTx.sign(pKeychain)
const txid: string = await pchain.issueTx(tx)
console.log(`Success! TXID: ${txid}`)
}
}

const sendMultisigAliasTxUpdate = async (): Promise<any> => {
const pKeychain = pchain.keyChain()
pKeychain.importKey(privKey1)
pKeychain.importKey(privKey2)
const pAddresses = pchain.keyChain().getAddresses()
const pAddressStrings = pchain.keyChain().getAddressStrings()

// Those are not serialized back and forth because
// its so simple and has no methods
let signatures: [string, string][] = []

// these are serialized to test if their methods are
// working properly
let unsignedTxHex: string = ""
let outputOwnersHex: string = ""

// simulate tx creation
{
const alias = await pchain.getMultisigAlias(msigAlias)

const newMemo = "101"

const platformVMUTXOResponse: any = await pchain.getUTXOs([msigAlias])
const utxoSet: UTXOSet = platformVMUTXOResponse.utxos

const multisigAliasParams: MultisigAliasParams = {
id: pchain.parseAddress(msigAlias),
memo: newMemo,
owners: new OutputOwners(
alias.addresses.map((address: string) => pchain.parseAddress(address)),
new BN(0),
1
),
auth: [[0, pchain.parseAddress(msigAlias)]]
}

const unsignedTx: UnsignedTx = await pchain.buildMultisigAliasTx(
utxoSet,
[[msigAlias], pAddressStrings],
[msigAlias],
multisigAliasParams,
undefined,
asOf,
alias.threshold
)

// turn it into a hex blob
unsignedTxHex = unsignedTx.toBuffer().toString("hex")
outputOwnersHex = OutputOwners.toArray(
unsignedTx.getTransaction().getOutputOwners()
).toString("hex")

// simulate signing
{
// deserialize
let unsignedTx = new UnsignedTx()
unsignedTx.fromBuffer(Buffer.from(unsignedTxHex, "hex"))

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

for (let address of pAddresses) {
// We need the keychain for signing
const keyPair = pKeychain.getKey(address)
// The signature
const signature = keyPair.sign(msg)
// save the signature
signatures.push([keyPair.getAddressString(), signature.toString("hex")])
}
}

// simulate reconstruciton
{
// load msig configuration from node
const msigAliasBuffer = pchain.parseAddress(msigAlias)
const owner = await pchain.getMultisigAlias(msigAlias)

// deserialize
let unsignedTx = new UnsignedTx()
unsignedTx.fromBuffer(Buffer.from(unsignedTxHex, "hex"))

// parse and set output owners - are requried for msig resolving
let parsedOwners: OutputOwners[] = OutputOwners.fromArray(
Buffer.from(outputOwnersHex, "hex")
)
unsignedTx.getTransaction().setOutputOwners(parsedOwners)

const txbuff = unsignedTx.toBuffer()
const msg: Buffer = Buffer.from(
createHash("sha256").update(txbuff).digest()
)

// create MSKeychein to create proper signidx
const msKeyChain = new MultisigKeyChain(
avalanche.getHRP(),
PChainAlias,
msg,
PlatformVMConstants.SECPMULTISIGCREDENTIAL,
unsignedTx.getTransaction().getOutputOwners(),
new Map([
[
msigAliasBuffer.toString("hex"),
new OutputOwners(
owner.addresses.map((a) => bintools.parseAddress(a, "P")),
new BN(owner.locktime),
owner.threshold
)
]
])
)

// load the signatures from the store/map/signavault
for (let [addressString, hexSignature] of signatures) {
let address = pchain.parseAddress(addressString)
let signature = Buffer.from(hexSignature, "hex")
msKeyChain.addKey(new MultisigKeyPair(msKeyChain, address, signature))
}

msKeyChain.buildSignatureIndices()

// Apply the signatures and send the tx
const tx: Tx = unsignedTx.sign(msKeyChain)
const txid: string = await pchain.issueTx(tx)
console.log(`Success! TXID: ${txid}`)
}
}
}

const main = async (): Promise<any> => {
await InitAvalanche()
try {
await sendMultisigAliasTxCreate()
await sendMultisigAliasTxUpdate()
} catch (e) {
console.log(e)
}
}

main()
66 changes: 65 additions & 1 deletion src/apis/platformvm/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ import {
GetDepositsParams,
GetDepositsResponse,
Owner,
OwnerParam
OwnerParam,
MultisigAliasParams
} from "./interfaces"
import { TransferableInput } from "./inputs"
import { TransferableOutput } from "./outputs"
Expand Down Expand Up @@ -2697,6 +2698,69 @@ export class PlatformVMAPI extends JRPCAPI {
return unsignedClaimTx
}

/**
* Build an unsigned [[MultisigAliasTx]].
*
* @param utxoset A set of UTXOs that the transaction is built on
* @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer}
* @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs.
* @param multisigAliasParams An object containing the parameters for the multisigAliasTx
* @param memo Optional contains arbitrary bytes, up to 256 bytes
* @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN}
* @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO
*
* @returns An unsigned transaction created from the passed in parameters.
*/
buildMultisigAliasTx = async (
utxoset: UTXOSet,
fromAddresses: FromType,
changeAddresses: string[],
multisigAliasParams: MultisigAliasParams,
memo: PayloadBase | Buffer = undefined,
asOf: BN = ZeroBN,
changeThreshold: number = 1
): Promise<UnsignedTx> => {
const caller = "buildMultisigAliasTx"

const fromSigner = this._parseFromSigner(fromAddresses, caller)

const change: Buffer[] = this._cleanAddressArrayBuffer(
changeAddresses,
caller
)

if (memo instanceof PayloadBase) {
memo = memo.getPayload()
}

const avaxAssetID: Buffer = await this.getAVAXAssetID()
const networkID: number = this.core.getNetworkID()
const blockchainID: Buffer = bintools.cb58Decode(this.blockchainID)
const fee: BN = this.getTxFee()

const builtUnsignedTx: UnsignedTx = await this._getBuilder(
utxoset
).buildMultisigAliasTx(
networkID,
blockchainID,
fromSigner,
change,
multisigAliasParams,
fee,
avaxAssetID,
memo,
asOf,
changeThreshold
)

if (!(await this.checkGooseEgg(builtUnsignedTx, this.getCreationTxFee()))) {
/* istanbul ignore next */
throw new GooseEggCheckError("Failed Goose Egg Check")
}

return builtUnsignedTx
}

/**
* @ignore
*/
Expand Down
Loading

0 comments on commit 74a8236

Please sign in to comment.