mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-01-31 15:22:16 +00:00
Support parsing and signature checking of alert messages. Very basic support, checking for version matches and relaying can come later.
This commit is contained in:
parent
3aafe80d99
commit
10c936c601
244
src/com/google/bitcoin/core/AlertMessage.java
Normal file
244
src/com/google/bitcoin/core/AlertMessage.java
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alerts are signed messages that are broadcast on the peer-to-peer network if they match a hard-coded signing key.
|
||||||
|
* The private keys are held by a small group of core Bitcoin developers, and alerts may be broadcast in the event of
|
||||||
|
* an available upgrade or a serious network problem. Alerts have an expiration time, data that specifies what
|
||||||
|
* set of software versions it matches and the ability to cancel them by broadcasting another type of alert.<p>
|
||||||
|
*
|
||||||
|
* The right course of action on receiving an alert is usually to either ensure a human will see it (display on screen,
|
||||||
|
* log, email), or if you decide to use alerts for notifications that are specific to your app in some way, to parse it.
|
||||||
|
* For example, you could treat it as an upgrade notification specific to your app. Satoshi designed alerts to ensure
|
||||||
|
* that software upgrades could be distributed independently of a hard-coded website, in order to allow everything to
|
||||||
|
* be purely peer-to-peer. You don't have to use this of course, and indeed it often makes more sense not to.<p>
|
||||||
|
*
|
||||||
|
* Before doing anything with an alert, you should check {@link AlertMessage#isSignatureValid()}.
|
||||||
|
*/
|
||||||
|
public class AlertMessage extends Message {
|
||||||
|
private byte[] content;
|
||||||
|
private byte[] signature;
|
||||||
|
|
||||||
|
// See the getters for documentation of what each field means.
|
||||||
|
private long version = 1;
|
||||||
|
private Date relayUntil;
|
||||||
|
private Date expiration;
|
||||||
|
private long id;
|
||||||
|
private long cancel;
|
||||||
|
private Set<Long> cancelSet;
|
||||||
|
private long minVer, maxVer;
|
||||||
|
private Set<String> matchingSubVers;
|
||||||
|
private long priority;
|
||||||
|
private String comment, statusBar, reserved;
|
||||||
|
|
||||||
|
// Chosen arbitrarily to avoid memory blowups.
|
||||||
|
private static final long MAX_SET_SIZE = 100;
|
||||||
|
|
||||||
|
public AlertMessage(NetworkParameters params, byte[] payloadBytes) throws ProtocolException {
|
||||||
|
super(params, payloadBytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void parse() throws ProtocolException {
|
||||||
|
// Alerts are formatted in two levels. The top level contains two byte arrays: a signature, and a serialized
|
||||||
|
// data structure containing the actual alert data.
|
||||||
|
int startPos = cursor;
|
||||||
|
content = readByteArray();
|
||||||
|
signature = readByteArray();
|
||||||
|
// Now we need to parse out the contents of the embedded structure. Rewind back to the start of the message.
|
||||||
|
cursor = startPos;
|
||||||
|
readVarInt(); // Skip the length field on the content array.
|
||||||
|
// We're inside the embedded structure.
|
||||||
|
version = readUint32();
|
||||||
|
// Read the timestamps. Bitcoin uses seconds since the epoch.
|
||||||
|
relayUntil = new Date(readUint64().longValue() * 1000);
|
||||||
|
expiration = new Date(readUint64().longValue() * 1000);
|
||||||
|
id = readUint32();
|
||||||
|
cancel = readUint32();
|
||||||
|
// Sets are serialized as <len><item><item><item>....
|
||||||
|
long cancelSetSize = readVarInt();
|
||||||
|
if (cancelSetSize < 0 || cancelSetSize > MAX_SET_SIZE) {
|
||||||
|
throw new ProtocolException("Bad cancel set size: " + cancelSetSize);
|
||||||
|
}
|
||||||
|
// Using a hashset here is very inefficient given that this will normally be only one item. But Java doesn't
|
||||||
|
// make it easy to do better. What we really want is just an array-backed set.
|
||||||
|
cancelSet = new HashSet<Long>((int)cancelSetSize);
|
||||||
|
for (long i = 0; i < cancelSetSize; i++) {
|
||||||
|
cancelSet.add(readUint32());
|
||||||
|
}
|
||||||
|
minVer = readUint32();
|
||||||
|
maxVer = readUint32();
|
||||||
|
// Read the subver matching set.
|
||||||
|
long subverSetSize = readVarInt();
|
||||||
|
if (subverSetSize < 0 || subverSetSize > MAX_SET_SIZE) {
|
||||||
|
throw new ProtocolException("Bad subver set size: " + subverSetSize);
|
||||||
|
}
|
||||||
|
matchingSubVers = new HashSet<String>((int)subverSetSize);
|
||||||
|
for (long i = 0; i < subverSetSize; i++) {
|
||||||
|
matchingSubVers.add(readStr());
|
||||||
|
}
|
||||||
|
priority = readUint32();
|
||||||
|
comment = readStr();
|
||||||
|
statusBar = readStr();
|
||||||
|
reserved = readStr();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the digital signature attached to the message verifies. Don't do anything with the alert if it
|
||||||
|
* doesn't verify, because that would allow arbitrary attackers to spam your users.
|
||||||
|
*/
|
||||||
|
public boolean isSignatureValid() {
|
||||||
|
return ECKey.verify(Utils.doubleDigest(content), signature, params.alertSigningKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void parseLite() throws ProtocolException {
|
||||||
|
// Do nothing, lazy parsing isn't useful for alerts.
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Field accessors.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time at which the alert should stop being broadcast across the network. Note that you can still receive
|
||||||
|
* the alert after this time from other nodes if the alert still applies to them or to you.
|
||||||
|
*/
|
||||||
|
public Date getRelayUntil() {
|
||||||
|
return relayUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRelayUntil(Date relayUntil) {
|
||||||
|
this.relayUntil = relayUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time at which the alert ceases to be relevant. It should not be presented to the user or app administrator
|
||||||
|
* after this time.
|
||||||
|
*/
|
||||||
|
public Date getExpiration() {
|
||||||
|
return expiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiration(Date expiration) {
|
||||||
|
this.expiration = expiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The numeric identifier of this alert. Each alert should have a unique ID, but the signer can choose any number.
|
||||||
|
* If an alert is broadcast with a cancel field higher than this ID, this alert is considered cancelled.
|
||||||
|
* @return uint32
|
||||||
|
*/
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A marker that results in any alerts with an ID lower than this value to be considered cancelled.
|
||||||
|
* @return uint32
|
||||||
|
*/
|
||||||
|
public long getCancel() {
|
||||||
|
return cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCancel(long cancel) {
|
||||||
|
this.cancel = cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The inclusive lower bound on software versions that are considered for the purposes of this alert. The Satoshi
|
||||||
|
* client compares this against a protocol version field, but as long as the subVer field is used to restrict it your
|
||||||
|
* alerts could use any version numbers.
|
||||||
|
* @return uint32
|
||||||
|
*/
|
||||||
|
public long getMinVer() {
|
||||||
|
return minVer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinVer(long minVer) {
|
||||||
|
this.minVer = minVer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The inclusive upper bound on software versions considered for the purposes of this alert. The Satoshi
|
||||||
|
* client compares this against a protocol version field, but as long as the subVer field is used to restrict it your
|
||||||
|
* alerts could use any version numbers.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long getMaxVer() {
|
||||||
|
return maxVer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxVer(long maxVer) {
|
||||||
|
this.maxVer = maxVer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an integer ordering amongst simultaneously active alerts.
|
||||||
|
* @return uint32
|
||||||
|
*/
|
||||||
|
public long getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriority(long priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This field is unused. It is presumably intended for the author of the alert to provide a justification for it
|
||||||
|
* visible to protocol developers but not users.
|
||||||
|
*/
|
||||||
|
public String getComment() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComment(String comment) {
|
||||||
|
this.comment = comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string that is intended to display in the status bar of the official GUI client. It contains the user-visible
|
||||||
|
* message. English only.
|
||||||
|
*/
|
||||||
|
public String getStatusBar() {
|
||||||
|
return statusBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatusBar(String statusBar) {
|
||||||
|
this.statusBar = statusBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This field is never used.
|
||||||
|
*/
|
||||||
|
public String getReserved() {
|
||||||
|
return reserved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReserved(String reserved) {
|
||||||
|
this.reserved = reserved;
|
||||||
|
}
|
||||||
|
}
|
@ -334,6 +334,9 @@ public class BitcoinSerializer {
|
|||||||
return new VersionAck(params, payloadBytes);
|
return new VersionAck(params, payloadBytes);
|
||||||
} else if (command.equals("headers")) {
|
} else if (command.equals("headers")) {
|
||||||
return new HeadersMessage(params, payloadBytes);
|
return new HeadersMessage(params, payloadBytes);
|
||||||
|
} else if (command.equals("alert")) {
|
||||||
|
log.info("alert payload " + Utils.bytesToHexString(payloadBytes));
|
||||||
|
return new AlertMessage(params, payloadBytes);
|
||||||
} else {
|
} else {
|
||||||
log.warn("No support for deserializing message with name {}", command);
|
log.warn("No support for deserializing message with name {}", command);
|
||||||
return new UnknownMessage(params, command, payloadBytes);
|
return new UnknownMessage(params, command, payloadBytes);
|
||||||
|
@ -36,6 +36,11 @@ public class NetworkParameters implements Serializable {
|
|||||||
*/
|
*/
|
||||||
public static final int PROTOCOL_VERSION = 31800;
|
public static final int PROTOCOL_VERSION = 31800;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alert signing key originally owned by Satoshi, and now passed on to Gavin along with a few others.
|
||||||
|
*/
|
||||||
|
public static final byte[] SATOSHI_KEY = Hex.decode("04fc9702847840aaf195de8442ebecedf5b095cdbb9bc716bda9110971b28a49e0ead8564ff0db22209e0374782c093bb899692d524e9d6a6956e7c5ecbcd68284");
|
||||||
|
|
||||||
// TODO: Seed nodes and checkpoint values should be here as well.
|
// TODO: Seed nodes and checkpoint values should be here as well.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,6 +73,11 @@ public class NetworkParameters implements Serializable {
|
|||||||
* test and production BitCoin networks use 2 weeks (1209600 seconds).
|
* test and production BitCoin networks use 2 weeks (1209600 seconds).
|
||||||
*/
|
*/
|
||||||
public int targetTimespan;
|
public int targetTimespan;
|
||||||
|
/**
|
||||||
|
* The key used to sign {@link AlertMessage}s. You can use {@link ECKey#verify(byte[], byte[], byte[])} to verify
|
||||||
|
* signatures using it.
|
||||||
|
*/
|
||||||
|
public byte[] alertSigningKey;
|
||||||
|
|
||||||
private static Block createGenesis(NetworkParameters n) {
|
private static Block createGenesis(NetworkParameters n) {
|
||||||
Block genesisBlock = new Block(n);
|
Block genesisBlock = new Block(n);
|
||||||
@ -107,6 +117,7 @@ public class NetworkParameters implements Serializable {
|
|||||||
n.dumpedPrivateKeyHeader = 239;
|
n.dumpedPrivateKeyHeader = 239;
|
||||||
n.interval = INTERVAL;
|
n.interval = INTERVAL;
|
||||||
n.targetTimespan = TARGET_TIMESPAN;
|
n.targetTimespan = TARGET_TIMESPAN;
|
||||||
|
n.alertSigningKey = SATOSHI_KEY;
|
||||||
n.genesisBlock = createGenesis(n);
|
n.genesisBlock = createGenesis(n);
|
||||||
n.genesisBlock.setTime(1296688602L);
|
n.genesisBlock.setTime(1296688602L);
|
||||||
n.genesisBlock.setDifficultyTarget(0x1d07fff8L);
|
n.genesisBlock.setDifficultyTarget(0x1d07fff8L);
|
||||||
@ -132,6 +143,7 @@ public class NetworkParameters implements Serializable {
|
|||||||
n.dumpedPrivateKeyHeader = 128;
|
n.dumpedPrivateKeyHeader = 128;
|
||||||
n.interval = INTERVAL;
|
n.interval = INTERVAL;
|
||||||
n.targetTimespan = TARGET_TIMESPAN;
|
n.targetTimespan = TARGET_TIMESPAN;
|
||||||
|
n.alertSigningKey = SATOSHI_KEY;
|
||||||
n.genesisBlock = createGenesis(n);
|
n.genesisBlock = createGenesis(n);
|
||||||
n.genesisBlock.setDifficultyTarget(0x1d00ffffL);
|
n.genesisBlock.setDifficultyTarget(0x1d00ffffL);
|
||||||
n.genesisBlock.setTime(1231006505L);
|
n.genesisBlock.setTime(1231006505L);
|
||||||
|
55
tests/com/google/bitcoin/core/AlertMessageTest.java
Normal file
55
tests/com/google/bitcoin/core/AlertMessageTest.java
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.assertEquals;
|
||||||
|
import static junit.framework.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class AlertMessageTest {
|
||||||
|
private static final byte[] TEST_KEY_PRIV = Hex.decode("6421e091445ade4b24658e96aa60959ce800d8ea9e7bd8613335aa65ba8d840b");
|
||||||
|
private NetworkParameters params;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
ECKey key = new ECKey(new BigInteger(1, TEST_KEY_PRIV));
|
||||||
|
params = NetworkParameters.unitTests();
|
||||||
|
params.alertSigningKey = key.getPubKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deserialize() throws Exception {
|
||||||
|
// A CAlert taken from the reference implementation.
|
||||||
|
// TODO: This does not check the subVer or set fields. Support proper version matching.
|
||||||
|
final byte[] payload = Hex.decode("5c010000004544eb4e000000004192ec4e00000000eb030000e9030000000000000048ee00000088130000002f43416c6572742073797374656d20746573743a2020202020202020207665722e302e352e3120617661696c61626c6500473045022100ec799908c008b272d5e5cd5a824abaaac53d210cc1fa517d8e22a701ecdb9e7002206fa1e7e7c251d5ba0d7c1fe428fc1870662f2927531d1cad8d4581b45bc4f8a7");
|
||||||
|
AlertMessage alert = new AlertMessage(params, payload);
|
||||||
|
assertEquals(1324041285, alert.getRelayUntil().getTime() / 1000);
|
||||||
|
assertEquals(1324126785, alert.getExpiration().getTime() / 1000);
|
||||||
|
assertEquals(1003, alert.getId());
|
||||||
|
assertEquals(1001, alert.getCancel());
|
||||||
|
assertEquals(0, alert.getMinVer());
|
||||||
|
assertEquals(61000, alert.getMaxVer());
|
||||||
|
assertEquals(5000, alert.getPriority());
|
||||||
|
assertEquals("CAlert system test: ver.0.5.1 available", alert.getStatusBar());
|
||||||
|
assertTrue(alert.isSignatureValid());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user