add P2WSH and P2WPKH address modes for addressFromPublicKeys()

This commit is contained in:
Faried Nawaz
2021-06-23 03:49:23 +05:00
committed by Reed Rosenbluth
parent d7f32a24a4
commit da31bf1c4d
3 changed files with 134 additions and 3 deletions

View File

@@ -19,6 +19,8 @@ import {
hashP2PKH,
rightPadHexToLength,
hashP2SH,
hashP2WSH,
hashP2WPKH,
} from './utils';
import { c32addressDecode, c32address } from 'c32check';
@@ -210,11 +212,14 @@ export function addressFromPublicKeys(
switch (hashMode) {
case AddressHashMode.SerializeP2PKH:
return addressFromVersionHash(version, hashP2PKH(publicKeys[0].data));
case AddressHashMode.SerializeP2WPKH:
return addressFromVersionHash(version, hashP2WPKH(publicKeys[0].data));
case AddressHashMode.SerializeP2SH:
return addressFromVersionHash(version, hashP2SH(numSigs, publicKeys.map(serializePublicKey)));
default:
throw Error(
`Not yet implemented: address construction using public keys for hash mode: ${hashMode}`
case AddressHashMode.SerializeP2WSH:
return addressFromVersionHash(
version,
hashP2WSH(numSigs, publicKeys.map(serializePublicKey))
);
}
}

View File

@@ -109,6 +109,21 @@ export const hashP2PKH = (input: Buffer): string => {
return hash160(input).toString('hex');
};
// Internally, the Stacks blockchain encodes address the same as Bitcoin
// single-sig address over p2sh (p2h-p2wpkh)
export const hashP2WPKH = (input: Buffer): string => {
const keyHash = hash160(input);
const bufferArray = new BufferArray();
bufferArray.appendByte(0);
bufferArray.appendByte(keyHash.length);
bufferArray.push(keyHash);
const redeemScript = bufferArray.concatBuffer();
const redeemScriptHash = hash160(redeemScript);
return redeemScriptHash.toString('hex');
};
// Internally, the Stacks blockchain encodes address the same as Bitcoin
// multi-sig address (p2sh)
export const hashP2SH = (numSigs: number, pubKeys: Buffer[]): string => {
@@ -135,6 +150,40 @@ export const hashP2SH = (numSigs: number, pubKeys: Buffer[]): string => {
return redeemScriptHash.toString('hex');
};
// Internally, the Stacks blockchain encodes address the same as Bitcoin
// multisig address over p2sh (p2sh-p2wsh)
export const hashP2WSH = (numSigs: number, pubKeys: Buffer[]): string => {
if (numSigs > 15 || pubKeys.length > 15) {
throw Error('P2WSH multisig address can only contain up to 15 public keys');
}
// construct P2SH script
const scriptArray = new BufferArray();
// OP_n
scriptArray.appendByte(80 + numSigs);
// public keys prepended by their length
pubKeys.forEach(pubKey => {
scriptArray.appendByte(pubKey.length);
scriptArray.push(pubKey);
});
// OP_m
scriptArray.appendByte(80 + pubKeys.length);
// OP_CHECKMULTISIG
scriptArray.appendByte(174);
const script = scriptArray.concatBuffer();
const digest = new sha256().update(script).digest();
const bufferArray = new BufferArray();
bufferArray.appendByte(0);
bufferArray.appendByte(digest.length);
bufferArray.push(digest);
const redeemScript = bufferArray.concatBuffer();
const redeemScriptHash = hash160(redeemScript);
return redeemScriptHash.toString('hex');
};
export function isClarityName(name: string) {
const regex = /^[a-zA-Z]([a-zA-Z0-9]|[-_!?+<>=/*])*$|^[-+=/*]$|^[<>]=?$/;
return regex.test(name) && name.length < 128;

View File

@@ -11,12 +11,14 @@ import {
addressFromHashMode,
createAssetInfo,
LengthPrefixedList,
addressFromPublicKeys,
} from '../src/types';
import { TransactionVersion, AddressHashMode, StacksMessageType } from '../src/constants';
import { serializeDeserialize } from './macros';
import { BufferReader } from '../src/bufferReader';
import { createStacksPublicKey } from '../src/keys';
test('Length prefixed strings serialization and deserialization', () => {
const testString = 'test message string';
@@ -105,6 +107,31 @@ test('C32 address hash mode - testnet P2SH', () => {
expect(address).toBe(expected);
});
test('C32 address hash mode - mainnet P2WSH', () => {
const address = addressToString(
addressFromHashMode(
AddressHashMode.SerializeP2WSH,
TransactionVersion.Mainnet,
'55011fc38a7e12f7d00496aef7a1c4b6dfeba81b'
)
);
const expected = 'SM1AG27Y3H9Z15XYG0JBAXXX1RJVDZTX83FA1DDSJ';
expect(address).toBe(expected);
});
test('C32 address hash mode - testnet P2WSH', () => {
const address = addressToString(
addressFromHashMode(
AddressHashMode.SerializeP2WSH,
TransactionVersion.Testnet,
'55011fc38a7e12f7d00496aef7a1c4b6dfeba81b'
)
);
const expected = 'SN1AG27Y3H9Z15XYG0JBAXXX1RJVDZTX83DE2F6ME';
expect(address).toBe(expected);
});
test('C32check addresses serialization and deserialization', () => {
const c32AddressString = 'SP9YX31TK12T0EZKWP3GZXX8AM37JDQHAWM7VBTH';
const addr = createAddress(c32AddressString);
@@ -122,3 +149,53 @@ test('Asset info serialization and deserialization', () => {
expect(deserialized.contractName.content).toBe(assetContractName);
expect(deserialized.assetName.content).toBe(assetName);
});
// address/mod.rs: test_public_keys_to_address_hash()
test('Public keys to address hash', () => {
const fixtures = [
{
keys: [createStacksPublicKey('040fadbbcea0ff3b05f03195b41cd991d7a0af8bd38559943aec99cbdaf0b22cc806b9a4f07579934774cc0c155e781d45c989f94336765e88a66d91cfb9f060b0')],
numRequired: 1,
segwit: false,
result: '395f3643cea07ec4eec73b4d9a973dcce56b9bf1',
},
{
keys: [
createStacksPublicKey('040fadbbcea0ff3b05f03195b41cd991d7a0af8bd38559943aec99cbdaf0b22cc806b9a4f07579934774cc0c155e781d45c989f94336765e88a66d91cfb9f060b0'),
createStacksPublicKey('04c77f262dda02580d65c9069a8a34c56bd77325bba4110b693b90216f5a3edc0bebc8ce28d61aa86b414aa91ecb29823b11aeed06098fcd97fee4bc73d54b1e96')
],
numRequired: 2,
segwit: false,
result: 'fd3a5e9f5ba311ce6122765f0af8da7488e25d3a',
},
{
keys: [createStacksPublicKey('020fadbbcea0ff3b05f03195b41cd991d7a0af8bd38559943aec99cbdaf0b22cc8')],
numRequired: 1,
segwit: true,
result: '0ac7ad046fe22c794dd923b3be14b2e668e50c42',
},
{
keys: [createStacksPublicKey('020fadbbcea0ff3b05f03195b41cd991d7a0af8bd38559943aec99cbdaf0b22cc8'),
createStacksPublicKey('02c77f262dda02580d65c9069a8a34c56bd77325bba4110b693b90216f5a3edc0b')],
numRequired: 2,
segwit: true,
result: '3e02fa83ac2fae11fd6703b91e7c94ad393052e2',
}
];
for (const fixture of fixtures) {
let hashMode;
if (!fixture.segwit) {
if (fixture.numRequired === 1) hashMode = AddressHashMode.SerializeP2PKH;
else hashMode = AddressHashMode.SerializeP2SH;
} else {
if (fixture.numRequired === 1) hashMode = AddressHashMode.SerializeP2WPKH;
else hashMode = AddressHashMode.SerializeP2WSH;
}
const address = addressFromPublicKeys(0, hashMode, fixture.numRequired, fixture.keys);
expect(address.hash160).toBe(fixture.result);
}
});